1425 lines
54 KiB
C
1425 lines
54 KiB
C
/** @file
|
|
rtl_433, turns your Realtek RTL2832 based DVB dongle into a 433.92MHz generic data receiver.
|
|
|
|
Copyright (C) 2012 by Benjamin Larsson <benjamin@southpole.se>
|
|
|
|
Based on rtl_sdr
|
|
Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
|
|
#include "rtl_433.h"
|
|
#include "r_private.h"
|
|
#include "r_device.h"
|
|
#include "rtl_433_devices.h"
|
|
#include "r_api.h"
|
|
#include "sdr.h"
|
|
#include "baseband.h"
|
|
#include "pulse_detect.h"
|
|
#include "pulse_demod.h"
|
|
#include "data.h"
|
|
#include "r_util.h"
|
|
#include "optparse.h"
|
|
#include "fileformat.h"
|
|
#include "samp_grab.h"
|
|
#include "am_analyze.h"
|
|
#include "confparse.h"
|
|
#include "term_ctl.h"
|
|
#include "compat_paths.h"
|
|
#include "fatal.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#ifdef _MSC_VER
|
|
#define F_OK 0
|
|
#endif
|
|
#endif
|
|
#ifndef _MSC_VER
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifndef _MSC_VER
|
|
#include <getopt.h>
|
|
#else
|
|
#include "getopt/getopt.h"
|
|
#endif
|
|
|
|
r_device *flex_create_device(char *spec); // maybe put this in some header file?
|
|
|
|
static void print_version(void)
|
|
{
|
|
fprintf(stderr, "%s\n", version_string());
|
|
fprintf(stderr, "Use -h for usage help and see https://triq.org/ for documentation.\n");
|
|
}
|
|
|
|
static void usage(int exit_code)
|
|
{
|
|
term_help_printf(
|
|
"Generic RF data receiver and decoder for ISM band devices using RTL-SDR and SoapySDR.\n"
|
|
"\nUsage:\n"
|
|
"\t\t= General options =\n"
|
|
" [-V] Output the version string and exit\n"
|
|
" [-v] Increase verbosity (can be used multiple times).\n"
|
|
" -v : verbose, -vv : verbose decoders, -vvv : debug decoders, -vvvv : trace decoding).\n"
|
|
" [-c <path>] Read config options from a file\n"
|
|
"\t\t= Tuner options =\n"
|
|
" [-d <RTL-SDR USB device index> | :<RTL-SDR USB device serial> | <SoapySDR device query> | rtl_tcp | help]\n"
|
|
" [-g <gain> | help] (default: auto)\n"
|
|
" [-t <settings>] apply a list of keyword=value settings for SoapySDR devices\n"
|
|
" e.g. -t \"antenna=A,bandwidth=4.5M,rfnotch_ctrl=false\"\n"
|
|
" [-f <frequency>] Receive frequency(s) (default: %i Hz)\n"
|
|
" [-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"
|
|
"\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"
|
|
" [-G] Enable blacklisted device decoding protocols, for testing only.\n"
|
|
" [-X <spec> | help] Add a general purpose decoder (prepend -R 0 to disable all decoders)\n"
|
|
" [-l <level>] Change detection level used to determine pulses (0-16384) (0=auto) (default: %i)\n"
|
|
" [-z <value>] Override short value in data decoder\n"
|
|
" [-x <value>] Override long value in data decoder\n"
|
|
" [-n <value>] Specify number of samples to take (each sample is 2 bytes: 1 each of I & Q)\n"
|
|
"\t\t= Analyze/Debug options =\n"
|
|
" [-a] Analyze mode. Print a textual description of the signal.\n"
|
|
" [-A] Pulse Analyzer. Enable pulse analysis and decode attempt.\n"
|
|
" Disable all decoders with -R 0 if you want analyzer output only.\n"
|
|
" [-y <code>] Verify decoding of demodulated test data (e.g. \"{25}fb2dd58\") with enabled devices\n"
|
|
"\t\t= File I/O options =\n"
|
|
" [-S none | all | unknown | known] Signal auto save. Creates one file per signal.\n"
|
|
" Note: Saves raw I/Q samples (uint8 pcm, 2 channel). Preferred mode for generating test files.\n"
|
|
" [-r <filename> | help] Read data from input file instead of a receiver\n"
|
|
" [-w <filename> | help] Save data stream to output file (a '-' dumps samples to stdout)\n"
|
|
" [-W <filename> | help] Save data stream to output file, overwrite existing file\n"
|
|
"\t\t= Data output options =\n"
|
|
" [-F kv | json | csv | mqtt | syslog | null | help] Produce decoded output in given format.\n"
|
|
" Append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.\n"
|
|
" Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514\n"
|
|
" [-M time[:<options>] | protocol | level | stats | bits | help] Add various meta data to each output.\n"
|
|
" [-K FILE | PATH | <tag>] Add an expanded token or fixed tag to every output line.\n"
|
|
" [-C native | si | customary] Convert units in decoded output.\n"
|
|
" [-T <seconds>] Specify number of seconds to run, also 12:34 or 1h23m45s\n"
|
|
" [-E hop | quit] Hop/Quit after outputting successful event(s)\n"
|
|
" [-h] Output this usage help and exit\n"
|
|
" Use -d, -g, -R, -X, -F, -M, -r, -w, or -W without argument for more help\n\n",
|
|
DEFAULT_FREQUENCY, DEFAULT_HOP_TIME, DEFAULT_SAMPLE_RATE, DEFAULT_LEVEL_LIMIT);
|
|
exit(exit_code);
|
|
}
|
|
|
|
static void help_protocols(r_device *devices, unsigned num_devices, int exit_code)
|
|
{
|
|
unsigned i;
|
|
char disabledc;
|
|
|
|
if (devices) {
|
|
term_help_printf("\t\t= Supported device protocols =\n");
|
|
for (i = 0; i < num_devices; i++) {
|
|
disabledc = devices[i].disabled ? '*' : ' ';
|
|
if (devices[i].disabled <= 2) // if not hidden
|
|
fprintf(stderr, " [%02d]%c %s\n", i + 1, disabledc, devices[i].name);
|
|
}
|
|
fprintf(stderr, "\n* Disabled by default, use -R n or -G\n");
|
|
}
|
|
exit(exit_code);
|
|
}
|
|
|
|
static void help_device(void)
|
|
{
|
|
term_help_printf(
|
|
"\t\t= Input device selection =\n"
|
|
#ifdef RTLSDR
|
|
"\tRTL-SDR device driver is available.\n"
|
|
#else
|
|
"\tRTL-SDR device driver is not available.\n"
|
|
#endif
|
|
" [-d <RTL-SDR USB device index>] (default: 0)\n"
|
|
" [-d :<RTL-SDR USB device serial (can be set with rtl_eeprom -s)>]\n"
|
|
"\tTo set gain for RTL-SDR use -g <gain> to set an overall gain in dB.\n"
|
|
#ifdef SOAPYSDR
|
|
"\tSoapySDR device driver is available.\n"
|
|
#else
|
|
"\tSoapySDR device driver is not available.\n"
|
|
#endif
|
|
" [-d \"\"] Open default SoapySDR device\n"
|
|
" [-d driver=rtlsdr] Open e.g. specific SoapySDR device\n"
|
|
"\tTo set gain for SoapySDR use -g ELEM=val,ELEM=val,... e.g. -g LNA=20,TIA=8,PGA=2 (for LimeSDR).\n"
|
|
" [-d rtl_tcp[:[//]host[:port]] (default: localhost:1234)\n"
|
|
"\tSpecify host/port to connect to with e.g. -d rtl_tcp:127.0.0.1:1234\n");
|
|
exit(0);
|
|
}
|
|
|
|
static void help_gain(void)
|
|
{
|
|
term_help_printf(
|
|
"\t\t= Gain option =\n"
|
|
" [-g <gain>] (default: auto)\n"
|
|
"\tFor RTL-SDR: gain in dB (\"0\" is auto).\n"
|
|
"\tFor SoapySDR: gain in dB for automatic distribution (\"\" is auto), or string of gain elements.\n"
|
|
"\tE.g. \"LNA=20,TIA=8,PGA=2\" for LimeSDR.\n");
|
|
exit(0);
|
|
}
|
|
|
|
static void help_output(void)
|
|
{
|
|
term_help_printf(
|
|
"\t\t= Output format option =\n"
|
|
" [-F kv|json|csv|mqtt|syslog|null] Produce decoded output in given format.\n"
|
|
"\tWithout this option the default is KV output. Use \"-F null\" to remove the default.\n"
|
|
"\tAppend output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.\n"
|
|
"\tSpecify MQTT server with e.g. -F mqtt://localhost:1883\n"
|
|
"\tAdd MQTT options with e.g. -F \"mqtt://host:1883,opt=arg\"\n"
|
|
"\tMQTT options are: user=foo, pass=bar, retain[=0|1], <format>[=topic]\n"
|
|
"\tSupported MQTT formats: (default is all)\n"
|
|
"\t events: posts JSON event data\n"
|
|
"\t states: posts JSON state data\n"
|
|
"\t devices: posts device and sensor info in nested topics\n"
|
|
"\tThe topic string will expand keys like [/model]\n"
|
|
"\tE.g. -F \"mqtt://localhost:1883,user=USERNAME,pass=PASSWORD,retain=0,devices=rtl_433[/id]\"\n"
|
|
"\tSpecify host/port for syslog with e.g. -F syslog:127.0.0.1:1514\n");
|
|
exit(0);
|
|
}
|
|
|
|
static void help_meta(void)
|
|
{
|
|
term_help_printf(
|
|
"\t\t= Meta information option =\n"
|
|
" [-M time[:<options>]|protocol|level|stats|bits|newmodel] Add various metadata to every output line.\n"
|
|
"\tUse \"time\" to add current date and time meta data (preset for live inputs).\n"
|
|
"\tUse \"time:rel\" to add sample position meta data (preset for read-file and stdin).\n"
|
|
"\tUse \"time:unix\" to show the seconds since unix epoch as time meta data.\n"
|
|
"\tUse \"time:iso\" to show the time with ISO-8601 format (YYYY-MM-DD\"T\"hh:mm:ss).\n"
|
|
"\tUse \"time:off\" to remove time meta data.\n"
|
|
"\tUse \"time:usec\" to add microseconds to date time meta data.\n"
|
|
"\tUse \"time:utc\" to output time in UTC.\n"
|
|
"\t\t(this may also be accomplished by invocation with TZ environment variable set).\n"
|
|
"\t\t\"usec\" and \"utc\" can be combined with other options, eg. \"time:unix:utc:usec\".\n"
|
|
"\tUse \"protocol\" / \"noprotocol\" to output the decoder protocol number meta data.\n"
|
|
"\tUse \"level\" to add Modulation, Frequency, RSSI, SNR, and Noise meta data.\n"
|
|
"\tUse \"stats[:[<level>][:<interval>]]\" to report statistics (default: 600 seconds).\n"
|
|
"\t level 0: no report, 1: report successful devices, 2: report active devices, 3: report all\n"
|
|
"\tUse \"bits\" to add bit representation to code outputs (for debug).\n"
|
|
"\nNote:"
|
|
"\tUse \"newmodel\" to transition to new model keys. This will become the default someday.\n"
|
|
"\tA table of changes and discussion is at https://github.com/merbanan/rtl_433/pull/986.\n\n");
|
|
exit(0);
|
|
}
|
|
|
|
static void help_read(void)
|
|
{
|
|
term_help_printf(
|
|
"\t\t= Read file option =\n"
|
|
" [-r <filename>] Read data from input file instead of a receiver\n"
|
|
"\tParameters are detected from the full path, file name, and extension.\n\n"
|
|
"\tA center frequency is detected as (fractional) number suffixed with 'M',\n"
|
|
"\t'Hz', 'kHz', 'MHz', or 'GHz'.\n\n"
|
|
"\tA sample rate is detected as (fractional) number suffixed with 'k',\n"
|
|
"\t'sps', 'ksps', 'Msps', or 'Gsps'.\n\n"
|
|
"\tFile content and format are detected as parameters, possible options are:\n"
|
|
"\t'cu8', 'cs16', 'cf32' ('IQ' implied), and 'am.s16'.\n\n"
|
|
"\tParameters must be separated by non-alphanumeric chars and are case-insensitive.\n"
|
|
"\tOverrides can be prefixed, separated by colon (':')\n\n"
|
|
"\tE.g. default detection by extension: path/filename.am.s16\n"
|
|
"\tforced overrides: am:s16:path/filename.ext\n");
|
|
exit(0);
|
|
}
|
|
|
|
static void help_write(void)
|
|
{
|
|
term_help_printf(
|
|
"\t\t= Write file option =\n"
|
|
" [-w <filename>] Save data stream to output file (a '-' dumps samples to stdout)\n"
|
|
" [-W <filename>] Save data stream to output file, overwrite existing file\n"
|
|
"\tParameters are detected from the full path, file name, and extension.\n\n"
|
|
"\tFile content and format are detected as parameters, possible options are:\n"
|
|
"\t'cu8', 'cs16', 'cf32' ('IQ' implied),\n"
|
|
"\t'am.s16', 'am.f32', 'fm.s16', 'fm.f32',\n"
|
|
"\t'i.f32', 'q.f32', 'logic.u8', 'ook', and 'vcd'.\n\n"
|
|
"\tParameters must be separated by non-alphanumeric chars and are case-insensitive.\n"
|
|
"\tOverrides can be prefixed, separated by colon (':')\n\n"
|
|
"\tE.g. default detection by extension: path/filename.am.s16\n"
|
|
"\tforced overrides: am:s16:path/filename.ext\n");
|
|
exit(0);
|
|
}
|
|
|
|
static void sdr_callback(unsigned char *iq_buf, uint32_t len, void *ctx)
|
|
{
|
|
r_cfg_t *cfg = ctx;
|
|
struct dm_state *demod = cfg->demod;
|
|
char time_str[LOCAL_TIME_BUFLEN];
|
|
unsigned long n_samples;
|
|
|
|
for (size_t i = 0; i < cfg->output_handler.len; ++i) { // list might contain NULLs
|
|
data_output_poll(cfg->output_handler.elems[i]);
|
|
}
|
|
|
|
if (cfg->do_exit || cfg->do_exit_async)
|
|
return;
|
|
|
|
if ((cfg->bytes_to_read > 0) && (cfg->bytes_to_read <= len)) {
|
|
len = cfg->bytes_to_read;
|
|
cfg->do_exit = 1;
|
|
sdr_stop(cfg->dev);
|
|
}
|
|
|
|
get_time_now(&demod->now);
|
|
|
|
n_samples = len / 2 / demod->sample_size;
|
|
|
|
// age the frame position if there is one
|
|
if (demod->frame_start_ago)
|
|
demod->frame_start_ago += n_samples;
|
|
if (demod->frame_end_ago)
|
|
demod->frame_end_ago += n_samples;
|
|
|
|
#ifndef _WIN32
|
|
alarm(3); // require callback to run every 3 second, abort otherwise
|
|
#endif
|
|
|
|
if (demod->samp_grab) {
|
|
samp_grab_push(demod->samp_grab, iq_buf, len);
|
|
}
|
|
|
|
// AM demodulation
|
|
if (demod->sample_size == 1) { // CU8
|
|
envelope_detect(iq_buf, demod->buf.temp, n_samples);
|
|
//magnitude_true_cu8(iq_buf, demod->buf.temp, n_samples);
|
|
//magnitude_est_cu8(iq_buf, demod->buf.temp, n_samples);
|
|
} else { // CS16
|
|
//magnitude_true_cs16((int16_t *)iq_buf, demod->buf.temp, n_samples);
|
|
magnitude_est_cs16((int16_t *)iq_buf, demod->buf.temp, n_samples);
|
|
}
|
|
baseband_low_pass_filter(demod->buf.temp, demod->am_buf, n_samples, &demod->lowpass_filter_state);
|
|
|
|
// FM demodulation
|
|
if (demod->enable_FM_demod) {
|
|
if (demod->sample_size == 1) { // CU8
|
|
baseband_demod_FM(iq_buf, demod->buf.fm, n_samples, &demod->demod_FM_state);
|
|
} else { // CS16
|
|
baseband_demod_FM_cs16((int16_t *)iq_buf, demod->buf.fm, n_samples, &demod->demod_FM_state);
|
|
}
|
|
}
|
|
|
|
// Handle special input formats
|
|
if (demod->load_info.format == S16_AM) { // The IQ buffer is really AM demodulated data
|
|
memcpy(demod->am_buf, iq_buf, len);
|
|
} else if (demod->load_info.format == S16_FM) { // The IQ buffer is really FM demodulated data
|
|
// we would need AM for the envelope too
|
|
memcpy(demod->buf.fm, iq_buf, len);
|
|
}
|
|
|
|
int d_events = 0; // Sensor events successfully detected
|
|
if (demod->r_devs.len || demod->analyze_pulses || demod->dumper.len || demod->samp_grab) {
|
|
// Detect a package and loop through demodulators with pulse data
|
|
int package_type = PULSE_DATA_OOK; // Just to get us started
|
|
for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {
|
|
file_info_t const *dumper = *iter;
|
|
if (dumper->format == U8_LOGIC) {
|
|
memset(demod->u8_buf, 0, n_samples);
|
|
break;
|
|
}
|
|
}
|
|
while (package_type) {
|
|
int p_events = 0; // Sensor events successfully detected per package
|
|
package_type = pulse_detect_package(demod->pulse_detect, demod->am_buf, demod->buf.fm, n_samples, demod->level_limit, cfg->samp_rate, cfg->input_pos, &demod->pulse_data, &demod->fsk_pulse_data);
|
|
if (package_type) {
|
|
// new package: set a first frame start if we are not tracking one already
|
|
if (!demod->frame_start_ago)
|
|
demod->frame_start_ago = demod->pulse_data.start_ago;
|
|
// always update the last frame end
|
|
demod->frame_end_ago = demod->pulse_data.end_ago;
|
|
}
|
|
if (package_type == PULSE_DATA_OOK) {
|
|
calc_rssi_snr(cfg, &demod->pulse_data);
|
|
if (demod->analyze_pulses) fprintf(stderr, "Detected OOK package\t%s\n", time_pos_str(cfg, demod->pulse_data.start_ago, time_str));
|
|
|
|
p_events += run_ook_demods(&demod->r_devs, &demod->pulse_data);
|
|
cfg->frames_count++;
|
|
cfg->frames_events += p_events > 0;
|
|
|
|
for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {
|
|
file_info_t const *dumper = *iter;
|
|
if (dumper->format == VCD_LOGIC) pulse_data_print_vcd(dumper->file, &demod->pulse_data, '\'');
|
|
if (dumper->format == U8_LOGIC) pulse_data_dump_raw(demod->u8_buf, n_samples, cfg->input_pos, &demod->pulse_data, 0x02);
|
|
if (dumper->format == PULSE_OOK) pulse_data_dump(dumper->file, &demod->pulse_data);
|
|
}
|
|
|
|
if (cfg->verbosity > 2) pulse_data_print(&demod->pulse_data);
|
|
if (demod->analyze_pulses && (cfg->grab_mode <= 1 || (cfg->grab_mode == 2 && p_events == 0) || (cfg->grab_mode == 3 && p_events > 0)) ) {
|
|
pulse_analyzer(&demod->pulse_data, package_type);
|
|
}
|
|
|
|
} else if (package_type == PULSE_DATA_FSK) {
|
|
calc_rssi_snr(cfg, &demod->fsk_pulse_data);
|
|
if (demod->analyze_pulses) fprintf(stderr, "Detected FSK package\t%s\n", time_pos_str(cfg, demod->fsk_pulse_data.start_ago, time_str));
|
|
|
|
p_events += run_fsk_demods(&demod->r_devs, &demod->fsk_pulse_data);
|
|
cfg->frames_fsk++;
|
|
cfg->frames_events += p_events > 0;
|
|
|
|
for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {
|
|
file_info_t const *dumper = *iter;
|
|
if (dumper->format == VCD_LOGIC) pulse_data_print_vcd(dumper->file, &demod->fsk_pulse_data, '"');
|
|
if (dumper->format == U8_LOGIC) pulse_data_dump_raw(demod->u8_buf, n_samples, cfg->input_pos, &demod->fsk_pulse_data, 0x04);
|
|
if (dumper->format == PULSE_OOK) pulse_data_dump(dumper->file, &demod->fsk_pulse_data);
|
|
}
|
|
|
|
if (cfg->verbosity > 2) pulse_data_print(&demod->fsk_pulse_data);
|
|
if (demod->analyze_pulses && (cfg->grab_mode <= 1 || (cfg->grab_mode == 2 && p_events == 0) || (cfg->grab_mode == 3 && p_events > 0)) ) {
|
|
pulse_analyzer(&demod->fsk_pulse_data, package_type);
|
|
}
|
|
} // if (package_type == ...
|
|
d_events += p_events;
|
|
} // while (package_type)...
|
|
|
|
// add event counter to the frames currently tracked
|
|
demod->frame_event_count += d_events;
|
|
|
|
// end frame tracking if older than a whole buffer
|
|
if (demod->frame_start_ago && demod->frame_end_ago > n_samples) {
|
|
if (demod->samp_grab) {
|
|
if (cfg->grab_mode == 1
|
|
|| (cfg->grab_mode == 2 && demod->frame_event_count == 0)
|
|
|| (cfg->grab_mode == 3 && demod->frame_event_count > 0)) {
|
|
unsigned frame_pad = n_samples / 8; // this could also be a fixed value, e.g. 10000 samples
|
|
unsigned start_padded = demod->frame_start_ago + frame_pad;
|
|
unsigned end_padded = demod->frame_end_ago - frame_pad;
|
|
unsigned len_padded = start_padded - end_padded;
|
|
samp_grab_write(demod->samp_grab, len_padded, end_padded);
|
|
}
|
|
}
|
|
demod->frame_start_ago = 0;
|
|
demod->frame_event_count = 0;
|
|
}
|
|
|
|
// dump partial pulse_data for this buffer
|
|
for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {
|
|
file_info_t const *dumper = *iter;
|
|
if (dumper->format == U8_LOGIC) {
|
|
pulse_data_dump_raw(demod->u8_buf, n_samples, cfg->input_pos, &demod->pulse_data, 0x02);
|
|
pulse_data_dump_raw(demod->u8_buf, n_samples, cfg->input_pos, &demod->fsk_pulse_data, 0x04);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (demod->am_analyze) {
|
|
am_analyze(demod->am_analyze, demod->am_buf, n_samples, cfg->verbosity > 1, NULL);
|
|
}
|
|
|
|
for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {
|
|
file_info_t const *dumper = *iter;
|
|
if (!dumper->file
|
|
|| dumper->format == VCD_LOGIC
|
|
|| dumper->format == PULSE_OOK)
|
|
continue;
|
|
uint8_t *out_buf = iq_buf; // Default is to dump IQ samples
|
|
unsigned long out_len = n_samples * 2 * demod->sample_size;
|
|
|
|
if (dumper->format == CU8_IQ) {
|
|
if (demod->sample_size == 2) {
|
|
for (unsigned long n = 0; n < n_samples * 2; ++n)
|
|
((uint8_t *)demod->buf.temp)[n] = (((int16_t *)iq_buf)[n] >> 8) + 128; // scale Q0.15 to Q0.7
|
|
out_buf = (uint8_t *)demod->buf.temp;
|
|
out_len = n_samples * 2 * sizeof(uint8_t);
|
|
}
|
|
}
|
|
else if (dumper->format == CS16_IQ) {
|
|
if (demod->sample_size == 1) {
|
|
for (unsigned long n = 0; n < n_samples * 2; ++n)
|
|
((int16_t *)demod->buf.temp)[n] = (iq_buf[n] << 8) - 32768; // scale Q0.7 to Q0.15
|
|
out_buf = (uint8_t *)demod->buf.temp; // this buffer is too small if out_block_size is large
|
|
out_len = n_samples * 2 * sizeof(int16_t);
|
|
}
|
|
}
|
|
else if (dumper->format == CS8_IQ) {
|
|
if (demod->sample_size == 1) {
|
|
for (unsigned long n = 0; n < n_samples * 2; ++n)
|
|
((int8_t *)demod->buf.temp)[n] = (iq_buf[n] - 128);
|
|
}
|
|
else if (demod->sample_size == 2) {
|
|
for (unsigned long n = 0; n < n_samples * 2; ++n)
|
|
((int8_t *)demod->buf.temp)[n] = ((int16_t *)iq_buf)[n] >> 8;
|
|
}
|
|
out_buf = (uint8_t *)demod->buf.temp;
|
|
out_len = n_samples * 2 * sizeof(int8_t);
|
|
}
|
|
else if (dumper->format == CF32_IQ) {
|
|
if (demod->sample_size == 1) {
|
|
for (unsigned long n = 0; n < n_samples * 2; ++n)
|
|
((float *)demod->buf.temp)[n] = (iq_buf[n] - 128) / 128.0;
|
|
}
|
|
else if (demod->sample_size == 2) {
|
|
for (unsigned long n = 0; n < n_samples * 2; ++n)
|
|
((float *)demod->buf.temp)[n] = ((int16_t *)iq_buf)[n] / 32768.0;
|
|
}
|
|
out_buf = (uint8_t *)demod->buf.temp; // this buffer is too small if out_block_size is large
|
|
out_len = n_samples * 2 * sizeof(float);
|
|
}
|
|
else if (dumper->format == S16_AM) {
|
|
out_buf = (uint8_t *)demod->am_buf;
|
|
out_len = n_samples * sizeof(int16_t);
|
|
}
|
|
else if (dumper->format == S16_FM) {
|
|
out_buf = (uint8_t *)demod->buf.fm;
|
|
out_len = n_samples * sizeof(int16_t);
|
|
}
|
|
else if (dumper->format == F32_AM) {
|
|
for (unsigned long n = 0; n < n_samples; ++n)
|
|
demod->f32_buf[n] = demod->am_buf[n] * (1.0 / 0x8000); // scale from Q0.15
|
|
out_buf = (uint8_t *)demod->f32_buf;
|
|
out_len = n_samples * sizeof(float);
|
|
}
|
|
else if (dumper->format == F32_FM) {
|
|
for (unsigned long n = 0; n < n_samples; ++n)
|
|
demod->f32_buf[n] = demod->buf.fm[n] * (1.0 / 0x8000); // scale from Q0.15
|
|
out_buf = (uint8_t *)demod->f32_buf;
|
|
out_len = n_samples * sizeof(float);
|
|
}
|
|
else if (dumper->format == F32_I) {
|
|
if (demod->sample_size == 1)
|
|
for (unsigned long n = 0; n < n_samples; ++n)
|
|
demod->f32_buf[n] = (iq_buf[n * 2] - 128) * (1.0 / 0x80); // scale from Q0.7
|
|
else
|
|
for (unsigned long n = 0; n < n_samples; ++n)
|
|
demod->f32_buf[n] = ((int16_t *)iq_buf)[n * 2] * (1.0 / 0x8000); // scale from Q0.15
|
|
out_buf = (uint8_t *)demod->f32_buf;
|
|
out_len = n_samples * sizeof(float);
|
|
}
|
|
else if (dumper->format == F32_Q) {
|
|
if (demod->sample_size == 1)
|
|
for (unsigned long n = 0; n < n_samples; ++n)
|
|
demod->f32_buf[n] = (iq_buf[n * 2 + 1] - 128) * (1.0 / 0x80); // scale from Q0.7
|
|
else
|
|
for (unsigned long n = 0; n < n_samples; ++n)
|
|
demod->f32_buf[n] = ((int16_t *)iq_buf)[n * 2 + 1] * (1.0 / 0x8000); // scale from Q0.15
|
|
out_buf = (uint8_t *)demod->f32_buf;
|
|
out_len = n_samples * sizeof(float);
|
|
}
|
|
else if (dumper->format == U8_LOGIC) { // state data
|
|
out_buf = demod->u8_buf;
|
|
out_len = n_samples;
|
|
}
|
|
|
|
if (fwrite(out_buf, 1, out_len, dumper->file) != out_len) {
|
|
fprintf(stderr, "Short write, samples lost, exiting!\n");
|
|
sdr_stop(cfg->dev);
|
|
}
|
|
}
|
|
|
|
cfg->input_pos += n_samples;
|
|
if (cfg->bytes_to_read > 0)
|
|
cfg->bytes_to_read -= len;
|
|
|
|
if (cfg->after_successful_events_flag && (d_events > 0)) {
|
|
if (cfg->after_successful_events_flag == 1) {
|
|
cfg->do_exit = 1;
|
|
}
|
|
cfg->do_exit_async = 1;
|
|
#ifndef _WIN32
|
|
alarm(0); // cancel the watchdog timer
|
|
#endif
|
|
sdr_stop(cfg->dev);
|
|
}
|
|
|
|
time_t rawtime;
|
|
time(&rawtime);
|
|
int hop_index = cfg->hop_times > cfg->frequency_index ? cfg->frequency_index : cfg->hop_times - 1;
|
|
if (cfg->frequencies > 1 && difftime(rawtime, cfg->hop_start_time) > cfg->hop_time[hop_index]) {
|
|
cfg->do_exit_async = 1;
|
|
#ifndef _WIN32
|
|
alarm(0); // cancel the watchdog timer
|
|
#endif
|
|
sdr_stop(cfg->dev);
|
|
}
|
|
if (cfg->duration > 0 && rawtime >= cfg->stop_time) {
|
|
cfg->do_exit_async = cfg->do_exit = 1;
|
|
#ifndef _WIN32
|
|
alarm(0); // cancel the watchdog timer
|
|
#endif
|
|
sdr_stop(cfg->dev);
|
|
fprintf(stderr, "Time expired, exiting!\n");
|
|
}
|
|
if (cfg->stats_now || (cfg->report_stats && cfg->stats_interval && rawtime >= cfg->stats_time)) {
|
|
event_occurred_handler(cfg, create_report_data(cfg, cfg->stats_now ? 3 : cfg->report_stats));
|
|
flush_report_data(cfg);
|
|
if (rawtime >= cfg->stats_time)
|
|
cfg->stats_time += cfg->stats_interval;
|
|
if (cfg->stats_now)
|
|
cfg->stats_now--;
|
|
}
|
|
}
|
|
|
|
static int hasopt(int test, int argc, char *argv[], char const *optstring)
|
|
{
|
|
int opt;
|
|
|
|
optind = 1; // reset getopt
|
|
while ((opt = getopt(argc, argv, optstring)) != -1) {
|
|
if (opt == test || optopt == test)
|
|
return opt;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg);
|
|
|
|
#define OPTSTRING "hVvqDc:x:z:p:aAI:S:m:M:r:w:W:l:d:t:f:H:g:s:b:n:R:X:F:K:C:T:UGy:E:"
|
|
|
|
// these should match the short options exactly
|
|
static struct conf_keywords const conf_keywords[] = {
|
|
{"help", 'h'},
|
|
{"verbose", 'v'},
|
|
{"version", 'V'},
|
|
{"config_file", 'c'},
|
|
{"report_meta", 'M'},
|
|
{"device", 'd'},
|
|
{"settings", 't'},
|
|
{"gain", 'g'},
|
|
{"frequency", 'f'},
|
|
{"hop_interval", 'H'},
|
|
{"ppm_error", 'p'},
|
|
{"sample_rate", 's'},
|
|
{"protocol", 'R'},
|
|
{"decoder", 'X'},
|
|
{"register_all", 'G'},
|
|
{"out_block_size", 'b'},
|
|
{"level_limit", 'l'},
|
|
{"samples_to_read", 'n'},
|
|
{"analyze", 'a'},
|
|
{"analyze_pulses", 'A'},
|
|
{"include_only", 'I'},
|
|
{"read_file", 'r'},
|
|
{"write_file", 'w'},
|
|
{"overwrite_file", 'W'},
|
|
{"signal_grabber", 'S'},
|
|
{"override_short", 'z'},
|
|
{"override_long", 'x'},
|
|
{"output", 'F'},
|
|
{"output_tag", 'K'},
|
|
{"convert", 'C'},
|
|
{"duration", 'T'},
|
|
{"test_data", 'y'},
|
|
{"stop_after_successful_events", 'E'},
|
|
{NULL, 0}};
|
|
|
|
static void parse_conf_text(r_cfg_t *cfg, char *conf)
|
|
{
|
|
int opt;
|
|
char *arg;
|
|
char *p = conf;
|
|
|
|
if (!conf || !*conf)
|
|
return;
|
|
|
|
while ((opt = getconf(&p, conf_keywords, &arg)) != -1) {
|
|
parse_conf_option(cfg, opt, arg);
|
|
}
|
|
}
|
|
|
|
static void parse_conf_file(r_cfg_t *cfg, char const *path)
|
|
{
|
|
if (!path || !*path || !strcmp(path, "null") || !strcmp(path, "0"))
|
|
return;
|
|
|
|
char *conf = readconf(path);
|
|
parse_conf_text(cfg, conf);
|
|
//free(conf); // TODO: check no args are dangling, then use free
|
|
}
|
|
|
|
static void parse_conf_try_default_files(r_cfg_t *cfg)
|
|
{
|
|
char **paths = compat_get_default_conf_paths();
|
|
for (int a = 0; paths[a]; a++) {
|
|
fprintf(stderr, "Trying conf file at \"%s\"...\n", paths[a]);
|
|
if (hasconf(paths[a])) {
|
|
fprintf(stderr, "Reading conf from \"%s\".\n", paths[a]);
|
|
parse_conf_file(cfg, paths[a]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void parse_conf_args(r_cfg_t *cfg, int argc, char *argv[])
|
|
{
|
|
int opt;
|
|
|
|
optind = 1; // reset getopt
|
|
while ((opt = getopt(argc, argv, OPTSTRING)) != -1) {
|
|
if (opt == '?')
|
|
opt = optopt; // allow missing arguments
|
|
parse_conf_option(cfg, opt, optarg);
|
|
}
|
|
}
|
|
|
|
static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg)
|
|
{
|
|
int n;
|
|
r_device *flex_device;
|
|
|
|
if (arg && (!strcmp(arg, "help") || !strcmp(arg, "?"))) {
|
|
arg = NULL; // remove the arg if it's a request for the usage help
|
|
}
|
|
|
|
switch (opt) {
|
|
case 'h':
|
|
usage(0);
|
|
break;
|
|
case 'V':
|
|
exit(0); // we already printed the version
|
|
break;
|
|
case 'v':
|
|
if (!arg)
|
|
cfg->verbosity++;
|
|
else
|
|
cfg->verbosity = atobv(arg, 1);
|
|
break;
|
|
case 'c':
|
|
parse_conf_file(cfg, arg);
|
|
break;
|
|
case 'd':
|
|
if (!arg)
|
|
help_device();
|
|
|
|
cfg->dev_query = arg;
|
|
break;
|
|
case 't':
|
|
// this option changed, check and warn if old meaning is used
|
|
if (!arg || *arg == '-') {
|
|
fprintf(stderr, "test_mode (-t) is deprecated. Use -S none|all|unknown|known\n");
|
|
exit(1);
|
|
}
|
|
cfg->settings_str = arg;
|
|
break;
|
|
case 'f':
|
|
if (cfg->frequencies < MAX_FREQS)
|
|
cfg->frequency[cfg->frequencies++] = atouint32_metric(arg, "-f: ");
|
|
else
|
|
fprintf(stderr, "Max number of frequencies reached %d\n", MAX_FREQS);
|
|
break;
|
|
case 'H':
|
|
if (cfg->hop_times < MAX_FREQS)
|
|
cfg->hop_time[cfg->hop_times++] = atoi_time(arg, "-H: ");
|
|
else
|
|
fprintf(stderr, "Max number of hop times reached %d\n", MAX_FREQS);
|
|
break;
|
|
case 'g':
|
|
if (!arg)
|
|
help_gain();
|
|
|
|
cfg->gain_str = arg;
|
|
break;
|
|
case 'G':
|
|
if (atobv(arg, 1)) {
|
|
fprintf(stderr, "\n\tUse -G for testing only. Enable protocols with -R if you really need them.\n\n");
|
|
cfg->no_default_devices = 1;
|
|
register_all_protocols(cfg, 1);
|
|
}
|
|
break;
|
|
case 'p':
|
|
cfg->ppm_error = atobv(arg, 0);
|
|
break;
|
|
case 's':
|
|
cfg->samp_rate = atouint32_metric(arg, "-s: ");
|
|
break;
|
|
case 'b':
|
|
cfg->out_block_size = atouint32_metric(arg, "-b: ");
|
|
break;
|
|
case 'l':
|
|
cfg->demod->level_limit = atouint32_metric(arg, "-l: ");
|
|
break;
|
|
case 'n':
|
|
cfg->bytes_to_read = atouint32_metric(arg, "-n: ") * 2;
|
|
break;
|
|
case 'a':
|
|
if (atobv(arg, 1) && !cfg->demod->am_analyze)
|
|
cfg->demod->am_analyze = am_analyze_create();
|
|
break;
|
|
case 'A':
|
|
cfg->demod->analyze_pulses = atobv(arg, 1);
|
|
break;
|
|
case 'I':
|
|
fprintf(stderr, "include_only (-I) is deprecated. Use -S none|all|unknown|known\n");
|
|
exit(1);
|
|
break;
|
|
case 'r':
|
|
if (!arg)
|
|
help_read();
|
|
|
|
add_infile(cfg, arg);
|
|
// TODO: check_read_file_info()
|
|
break;
|
|
case 'w':
|
|
if (!arg)
|
|
help_write();
|
|
|
|
add_dumper(cfg, arg, 0);
|
|
break;
|
|
case 'W':
|
|
if (!arg)
|
|
help_write();
|
|
|
|
add_dumper(cfg, arg, 1);
|
|
break;
|
|
case 'S':
|
|
if (!arg)
|
|
usage(1);
|
|
if (strcasecmp(arg, "all") == 0)
|
|
cfg->grab_mode = 1;
|
|
else if (strcasecmp(arg, "unknown") == 0)
|
|
cfg->grab_mode = 2;
|
|
else if (strcasecmp(arg, "known") == 0)
|
|
cfg->grab_mode = 3;
|
|
else
|
|
cfg->grab_mode = atobv(arg, 1);
|
|
if (cfg->grab_mode && !cfg->demod->samp_grab)
|
|
cfg->demod->samp_grab = samp_grab_create(SIGNAL_GRABBER_BUFFER);
|
|
break;
|
|
case 'm':
|
|
fprintf(stderr, "sample mode option is deprecated.\n");
|
|
usage(1);
|
|
break;
|
|
case 'M':
|
|
if (!arg)
|
|
help_meta();
|
|
|
|
if (!strncasecmp(arg, "time", 4)) {
|
|
char *p = arg_param(arg);
|
|
// time time:1 time:on time:yes
|
|
// time:0 time:off time:no
|
|
// time:rel
|
|
// time:unix
|
|
// time:iso
|
|
// time:...:usec time:...:sec
|
|
// time:...:utc time:...:local
|
|
cfg->report_time = REPORT_TIME_DATE;
|
|
while (p && *p) {
|
|
if (!strncasecmp(p, "0", 1) || !strncasecmp(p, "no", 2) || !strncasecmp(p, "off", 3))
|
|
cfg->report_time = REPORT_TIME_OFF;
|
|
else if (!strncasecmp(p, "1", 1) || !strncasecmp(p, "yes", 3) || !strncasecmp(p, "on", 2))
|
|
cfg->report_time = REPORT_TIME_DATE;
|
|
else if (!strncasecmp(p, "rel", 3))
|
|
cfg->report_time = REPORT_TIME_SAMPLES;
|
|
else if (!strncasecmp(p, "unix", 4))
|
|
cfg->report_time = REPORT_TIME_UNIX;
|
|
else if (!strncasecmp(p, "iso", 3))
|
|
cfg->report_time = REPORT_TIME_ISO;
|
|
else if (!strncasecmp(p, "usec", 4))
|
|
cfg->report_time_hires = 1;
|
|
else if (!strncasecmp(p, "sec", 3))
|
|
cfg->report_time_hires = 0;
|
|
else if (!strncasecmp(p, "utc", 3))
|
|
cfg->report_time_utc = 1;
|
|
else if (!strncasecmp(p, "local", 5))
|
|
cfg->report_time_utc = 0;
|
|
else {
|
|
fprintf(stderr, "Unknown time format option: %s\n", p);
|
|
help_meta();
|
|
}
|
|
|
|
p = arg_param(p);
|
|
}
|
|
// fprintf(stderr, "time format: %d, usec:%d utc:%d\n", cfg->report_time, cfg->report_time_hires, cfg->report_time_utc);
|
|
}
|
|
|
|
// TODO: old time options, remove someday
|
|
else if (!strcasecmp(arg, "reltime"))
|
|
cfg->report_time = REPORT_TIME_SAMPLES;
|
|
else if (!strcasecmp(arg, "notime"))
|
|
cfg->report_time = REPORT_TIME_OFF;
|
|
else if (!strcasecmp(arg, "hires"))
|
|
cfg->report_time_hires = 1;
|
|
else if (!strcasecmp(arg, "utc"))
|
|
cfg->report_time_utc = 1;
|
|
else if (!strcasecmp(arg, "noutc"))
|
|
cfg->report_time_utc = 0;
|
|
|
|
else if (!strcasecmp(arg, "protocol"))
|
|
cfg->report_protocol = 1;
|
|
else if (!strcasecmp(arg, "noprotocol"))
|
|
cfg->report_protocol = 0;
|
|
else if (!strcasecmp(arg, "level"))
|
|
cfg->report_meta = 1;
|
|
else if (!strcasecmp(arg, "bits"))
|
|
cfg->verbose_bits = 1;
|
|
else if (!strcasecmp(arg, "description"))
|
|
cfg->report_description = 1;
|
|
else if (!strcasecmp(arg, "newmodel"))
|
|
cfg->new_model_keys = 1;
|
|
else if (!strcasecmp(arg, "oldmodel"))
|
|
cfg->new_model_keys = 0;
|
|
else if (!strncasecmp(arg, "stats", 5)) {
|
|
// there also should be options to set wether to flush on report
|
|
char *p = arg_param(arg);
|
|
cfg->report_stats = atoiv(p, 1);
|
|
cfg->stats_interval = atoiv(arg_param(p), 600);
|
|
time(&cfg->stats_time);
|
|
cfg->stats_time += cfg->stats_interval;
|
|
}
|
|
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':
|
|
if (cfg->demod->am_analyze)
|
|
cfg->demod->am_analyze->override_short = atoi(arg);
|
|
break;
|
|
case 'x':
|
|
if (cfg->demod->am_analyze)
|
|
cfg->demod->am_analyze->override_long = atoi(arg);
|
|
break;
|
|
case 'R':
|
|
if (!arg)
|
|
help_protocols(cfg->devices, cfg->num_r_devices, 0);
|
|
|
|
n = atoi(arg);
|
|
if (n > cfg->num_r_devices || -n > cfg->num_r_devices) {
|
|
fprintf(stderr, "Protocol number specified (%d) is larger than number of protocols\n\n", n);
|
|
help_protocols(cfg->devices, cfg->num_r_devices, 1);
|
|
}
|
|
if ((n > 0 && cfg->devices[n - 1].disabled > 2) || (n < 0 && cfg->devices[-n - 1].disabled > 2)) {
|
|
fprintf(stderr, "Protocol number specified (%d) is invalid\n\n", n);
|
|
help_protocols(cfg->devices, cfg->num_r_devices, 1);
|
|
}
|
|
|
|
if (n < 0 && !cfg->no_default_devices) {
|
|
register_all_protocols(cfg, 0); // register all defaults
|
|
}
|
|
cfg->no_default_devices = 1;
|
|
|
|
if (n >= 1) {
|
|
register_protocol(cfg, &cfg->devices[n - 1], arg_param(arg));
|
|
}
|
|
else if (n <= -1) {
|
|
unregister_protocol(cfg, &cfg->devices[-n - 1]);
|
|
}
|
|
else {
|
|
fprintf(stderr, "Disabling all device decoders.\n");
|
|
list_clear(&cfg->demod->r_devs, (list_elem_free_fn)free_protocol);
|
|
}
|
|
break;
|
|
case 'X':
|
|
if (!arg)
|
|
flex_create_device(NULL);
|
|
|
|
flex_device = flex_create_device(arg);
|
|
register_protocol(cfg, flex_device, "");
|
|
break;
|
|
case 'q':
|
|
fprintf(stderr, "quiet option (-q) is default and deprecated. See -v to increase verbosity\n");
|
|
break;
|
|
case 'F':
|
|
if (!arg)
|
|
help_output();
|
|
|
|
if (strncmp(arg, "json", 4) == 0) {
|
|
add_json_output(cfg, arg_param(arg));
|
|
}
|
|
else if (strncmp(arg, "csv", 3) == 0) {
|
|
add_csv_output(cfg, arg_param(arg));
|
|
}
|
|
else if (strncmp(arg, "kv", 2) == 0) {
|
|
add_kv_output(cfg, arg_param(arg));
|
|
}
|
|
else if (strncmp(arg, "mqtt", 4) == 0) {
|
|
add_mqtt_output(cfg, arg_param(arg));
|
|
}
|
|
else if (strncmp(arg, "syslog", 6) == 0) {
|
|
add_syslog_output(cfg, arg_param(arg));
|
|
}
|
|
else if (strncmp(arg, "null", 4) == 0) {
|
|
add_null_output(cfg, arg_param(arg));
|
|
}
|
|
else {
|
|
fprintf(stderr, "Invalid output format %s\n", arg);
|
|
usage(1);
|
|
}
|
|
break;
|
|
case 'K':
|
|
cfg->output_tag = arg;
|
|
break;
|
|
case 'C':
|
|
if (strcmp(arg, "native") == 0) {
|
|
cfg->conversion_mode = CONVERT_NATIVE;
|
|
}
|
|
else if (strcmp(arg, "si") == 0) {
|
|
cfg->conversion_mode = CONVERT_SI;
|
|
}
|
|
else if (strcmp(arg, "customary") == 0) {
|
|
cfg->conversion_mode = CONVERT_CUSTOMARY;
|
|
}
|
|
else {
|
|
fprintf(stderr, "Invalid conversion mode %s\n", arg);
|
|
usage(1);
|
|
}
|
|
break;
|
|
case 'U':
|
|
fprintf(stderr, "UTC mode option (-U) is deprecated. Please use \"-M utc\".\n");
|
|
exit(1);
|
|
break;
|
|
case 'T':
|
|
cfg->duration = atoi_time(arg, "-T: ");
|
|
if (cfg->duration < 1) {
|
|
fprintf(stderr, "Duration '%s' not a positive number; will continue indefinitely\n", arg);
|
|
}
|
|
break;
|
|
case 'y':
|
|
cfg->test_data = arg;
|
|
break;
|
|
case 'E':
|
|
if (arg && !strcmp(arg, "hop")) {
|
|
cfg->after_successful_events_flag = 2;
|
|
}
|
|
else if (arg && !strcmp(arg, "quit")) {
|
|
cfg->after_successful_events_flag = 1;
|
|
}
|
|
else {
|
|
cfg->after_successful_events_flag = atobv(arg, 1);
|
|
}
|
|
break;
|
|
default:
|
|
usage(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static r_cfg_t cfg;
|
|
|
|
// TODO: SIGINFO is not in POSIX...
|
|
#ifndef SIGINFO
|
|
#define SIGINFO 29
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
BOOL WINAPI
|
|
sighandler(int signum)
|
|
{
|
|
if (CTRL_C_EVENT == signum) {
|
|
fprintf(stderr, "Signal caught, exiting!\n");
|
|
cfg.do_exit = 1;
|
|
sdr_stop(cfg.dev);
|
|
return TRUE;
|
|
}
|
|
else if (CTRL_BREAK_EVENT == signum) {
|
|
fprintf(stderr, "CTRL-BREAK detected, hopping to next frequency (-f). Use CTRL-C to quit.\n");
|
|
cfg.do_exit_async = 1;
|
|
sdr_stop(cfg.dev);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
#else
|
|
static void sighandler(int signum)
|
|
{
|
|
if (signum == SIGPIPE) {
|
|
signal(SIGPIPE, SIG_IGN);
|
|
}
|
|
else if (signum == SIGINFO/* TODO: maybe SIGUSR1 */) {
|
|
cfg.stats_now++;
|
|
return;
|
|
}
|
|
else if (signum == SIGUSR1) {
|
|
cfg.do_exit_async = 1;
|
|
sdr_stop(cfg.dev);
|
|
return;
|
|
}
|
|
else if (signum == SIGALRM) {
|
|
fprintf(stderr, "Async read stalled, exiting!\n");
|
|
}
|
|
else {
|
|
fprintf(stderr, "Signal caught, exiting!\n");
|
|
}
|
|
cfg.do_exit = 1;
|
|
sdr_stop(cfg.dev);
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char **argv) {
|
|
#ifndef _WIN32
|
|
struct sigaction sigact;
|
|
#endif
|
|
FILE *in_file;
|
|
int r = 0;
|
|
unsigned i;
|
|
struct dm_state *demod;
|
|
|
|
print_version(); // always print the version info
|
|
|
|
r_init_cfg(&cfg);
|
|
|
|
setbuf(stdout, NULL);
|
|
setbuf(stderr, NULL);
|
|
|
|
demod = cfg.demod;
|
|
|
|
demod->pulse_detect = pulse_detect_create();
|
|
|
|
/* initialize tables */
|
|
baseband_init();
|
|
|
|
r_device r_devices[] = {
|
|
#define DECL(name) name,
|
|
DEVICES
|
|
#undef DECL
|
|
};
|
|
|
|
cfg.num_r_devices = sizeof(r_devices) / sizeof(*r_devices);
|
|
for (i = 0; i < cfg.num_r_devices; i++) {
|
|
r_devices[i].protocol_num = i + 1;
|
|
}
|
|
cfg.devices = r_devices;
|
|
|
|
// if there is no explicit conf file option look for default conf files
|
|
if (!hasopt('c', argc, argv, OPTSTRING)) {
|
|
parse_conf_try_default_files(&cfg);
|
|
}
|
|
|
|
parse_conf_args(&cfg, argc, argv);
|
|
|
|
// warn if still using old model keys
|
|
if (!cfg.new_model_keys) {
|
|
fprintf(stderr,
|
|
"\n\tConsider using \"-M newmodel\" to transition to new model keys. This will become the default someday.\n"
|
|
"\tA table of changes and discussion is at https://github.com/merbanan/rtl_433/pull/986.\n\n");
|
|
}
|
|
|
|
// add all remaining positional arguments as input files
|
|
while (argc > optind) {
|
|
add_infile(&cfg, argv[optind++]);
|
|
}
|
|
|
|
if (demod->am_analyze) {
|
|
demod->am_analyze->level_limit = &demod->level_limit;
|
|
demod->am_analyze->frequency = &cfg.center_frequency;
|
|
demod->am_analyze->samp_rate = &cfg.samp_rate;
|
|
demod->am_analyze->sample_size = &demod->sample_size;
|
|
}
|
|
|
|
if (demod->samp_grab) {
|
|
demod->samp_grab->frequency = &cfg.center_frequency;
|
|
demod->samp_grab->samp_rate = &cfg.samp_rate;
|
|
demod->samp_grab->sample_size = &demod->sample_size;
|
|
}
|
|
|
|
if (cfg.report_time == REPORT_TIME_DEFAULT) {
|
|
if (cfg.in_files.len)
|
|
cfg.report_time = REPORT_TIME_SAMPLES;
|
|
else
|
|
cfg.report_time = REPORT_TIME_DATE;
|
|
}
|
|
if (cfg.report_time_utc) {
|
|
#ifdef _WIN32
|
|
putenv("TZ=UTC+0");
|
|
_tzset();
|
|
#else
|
|
r = setenv("TZ", "UTC", 1);
|
|
if (r != 0)
|
|
fprintf(stderr, "Unable to set TZ to UTC; error code: %d\n", r);
|
|
#endif
|
|
}
|
|
|
|
if (!cfg.output_handler.len) {
|
|
add_kv_output(&cfg, NULL);
|
|
}
|
|
|
|
// register default decoders if nothing is configured
|
|
if (!cfg.no_default_devices) {
|
|
register_all_protocols(&cfg, 0); // register all defaults
|
|
} else {
|
|
update_protocols(&cfg);
|
|
}
|
|
|
|
// check if we need FM demod
|
|
for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {
|
|
r_device *r_dev = *iter;
|
|
if (r_dev->modulation >= FSK_DEMOD_MIN_VAL) {
|
|
demod->enable_FM_demod = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "Registered %zu out of %d device decoding protocols",
|
|
demod->r_devs.len, cfg.num_r_devices);
|
|
|
|
if (!cfg.verbosity) {
|
|
// print registered decoder ranges
|
|
fprintf(stderr, " [");
|
|
for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {
|
|
r_device *r_dev = *iter;
|
|
unsigned num = r_dev->protocol_num;
|
|
if (num == 0)
|
|
continue;
|
|
while (iter[1]
|
|
&& r_dev->protocol_num + 1 == ((r_device *)iter[1])->protocol_num)
|
|
r_dev = *++iter;
|
|
if (num == r_dev->protocol_num)
|
|
fprintf(stderr, " %d", num);
|
|
else
|
|
fprintf(stderr, " %d-%d", num, r_dev->protocol_num);
|
|
}
|
|
fprintf(stderr, " ]");
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
start_outputs(&cfg, well_known_output_fields(&cfg));
|
|
|
|
if (cfg.out_block_size < MINIMAL_BUF_LENGTH ||
|
|
cfg.out_block_size > MAXIMAL_BUF_LENGTH) {
|
|
fprintf(stderr,
|
|
"Output block size wrong value, falling back to default\n");
|
|
fprintf(stderr,
|
|
"Minimal length: %u\n", MINIMAL_BUF_LENGTH);
|
|
fprintf(stderr,
|
|
"Maximal length: %u\n", MAXIMAL_BUF_LENGTH);
|
|
cfg.out_block_size = DEFAULT_BUF_LENGTH;
|
|
}
|
|
|
|
// Special case for test data
|
|
if (cfg.test_data) {
|
|
r = 0;
|
|
for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {
|
|
r_device *r_dev = *iter;
|
|
if (cfg.verbosity)
|
|
fprintf(stderr, "Verifying test data with device %s.\n", r_dev->name);
|
|
r += pulse_demod_string(cfg.test_data, r_dev);
|
|
}
|
|
r_free_cfg(&cfg);
|
|
exit(!r);
|
|
}
|
|
|
|
// Special case for in files
|
|
if (cfg.in_files.len) {
|
|
unsigned char *test_mode_buf = malloc(DEFAULT_BUF_LENGTH * sizeof(unsigned char));
|
|
if (!test_mode_buf)
|
|
FATAL_MALLOC("test_mode_buf");
|
|
float *test_mode_float_buf = malloc(DEFAULT_BUF_LENGTH / sizeof(int16_t) * sizeof(float));
|
|
if (!test_mode_float_buf)
|
|
FATAL_MALLOC("test_mode_float_buf");
|
|
|
|
if (cfg.duration > 0) {
|
|
time(&cfg.stop_time);
|
|
cfg.stop_time += cfg.duration;
|
|
}
|
|
|
|
for (void **iter = cfg.in_files.elems; iter && *iter; ++iter) {
|
|
cfg.in_filename = *iter;
|
|
|
|
parse_file_info(cfg.in_filename, &demod->load_info);
|
|
if (strcmp(demod->load_info.path, "-") == 0) { /* read samples from stdin */
|
|
in_file = stdin;
|
|
cfg.in_filename = "<stdin>";
|
|
} else {
|
|
in_file = fopen(demod->load_info.path, "rb");
|
|
if (!in_file) {
|
|
fprintf(stderr, "Opening file: %s failed!\n", cfg.in_filename);
|
|
break;
|
|
}
|
|
}
|
|
fprintf(stderr, "Test mode active. Reading samples from file: %s\n", cfg.in_filename); // Essential information (not quiet)
|
|
if (demod->load_info.format == CU8_IQ
|
|
|| demod->load_info.format == S16_AM
|
|
|| demod->load_info.format == S16_FM) {
|
|
demod->sample_size = sizeof(uint8_t); // CU8, AM, FM
|
|
} else {
|
|
demod->sample_size = sizeof(int16_t); // CF32, CS16
|
|
}
|
|
if (cfg.verbosity) {
|
|
fprintf(stderr, "Input format: %s\n", file_info_string(&demod->load_info));
|
|
}
|
|
demod->sample_file_pos = 0.0;
|
|
|
|
// special case for pulse data file-inputs
|
|
if (demod->load_info.format == PULSE_OOK) {
|
|
while (!cfg.do_exit) {
|
|
pulse_data_load(in_file, &demod->pulse_data, cfg.samp_rate);
|
|
if (!demod->pulse_data.num_pulses)
|
|
break;
|
|
|
|
if (demod->pulse_data.fsk_f2_est) {
|
|
run_fsk_demods(&demod->r_devs, &demod->pulse_data);
|
|
}
|
|
else {
|
|
run_ook_demods(&demod->r_devs, &demod->pulse_data);
|
|
}
|
|
}
|
|
|
|
if (in_file != stdin)
|
|
fclose(in_file = stdin);
|
|
|
|
continue;
|
|
}
|
|
|
|
// default case for file-inputs
|
|
int n_blocks = 0;
|
|
unsigned long n_read;
|
|
do {
|
|
if (demod->load_info.format == CF32_IQ) {
|
|
n_read = fread(test_mode_float_buf, sizeof(float), DEFAULT_BUF_LENGTH / 2, in_file);
|
|
// clamp float to [-1,1] and scale to Q0.15
|
|
for(unsigned long n = 0; n < n_read; n++) {
|
|
int s_tmp = test_mode_float_buf[n] * INT16_MAX;
|
|
if (s_tmp < -INT16_MAX)
|
|
s_tmp = -INT16_MAX;
|
|
else if (s_tmp > INT16_MAX)
|
|
s_tmp = INT16_MAX;
|
|
((int16_t *)test_mode_buf)[n] = s_tmp;
|
|
}
|
|
n_read *= 2; // convert to byte count
|
|
} else {
|
|
n_read = fread(test_mode_buf, 1, DEFAULT_BUF_LENGTH, in_file);
|
|
}
|
|
if (n_read == 0) break; // sdr_callback() will Segmentation Fault with len=0
|
|
demod->sample_file_pos = ((float)n_blocks * DEFAULT_BUF_LENGTH + n_read) / cfg.samp_rate / 2 / demod->sample_size;
|
|
n_blocks++; // this assumes n_read == DEFAULT_BUF_LENGTH
|
|
sdr_callback(test_mode_buf, n_read, &cfg);
|
|
} while (n_read != 0 && !cfg.do_exit);
|
|
|
|
// Call a last time with cleared samples to ensure EOP detection
|
|
if (demod->sample_size == 1) { // CU8
|
|
memset(test_mode_buf, 128, DEFAULT_BUF_LENGTH); // 128 is 0 in unsigned data
|
|
// or is 127.5 a better 0 in cu8 data?
|
|
//for (unsigned long n = 0; n < DEFAULT_BUF_LENGTH/2; n++)
|
|
// ((uint16_t *)test_mode_buf)[n] = 0x807f;
|
|
}
|
|
else { // CF32, CS16
|
|
memset(test_mode_buf, 0, DEFAULT_BUF_LENGTH);
|
|
}
|
|
demod->sample_file_pos = ((float)n_blocks + 1) * DEFAULT_BUF_LENGTH / cfg.samp_rate / 2 / demod->sample_size;
|
|
sdr_callback(test_mode_buf, DEFAULT_BUF_LENGTH, &cfg);
|
|
|
|
//Always classify a signal at the end of the file
|
|
if (demod->am_analyze)
|
|
am_analyze_classify(demod->am_analyze);
|
|
if (cfg.verbosity) {
|
|
fprintf(stderr, "Test mode file issued %d packets\n", n_blocks);
|
|
}
|
|
|
|
if (in_file != stdin)
|
|
fclose(in_file = stdin);
|
|
}
|
|
|
|
free(test_mode_buf);
|
|
free(test_mode_float_buf);
|
|
r_free_cfg(&cfg);
|
|
exit(0);
|
|
}
|
|
|
|
// Normal case, no test data, no in files
|
|
r = sdr_open(&cfg.dev, &demod->sample_size, cfg.dev_query, cfg.verbosity);
|
|
if (r < 0) {
|
|
exit(1);
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
sigact.sa_handler = sighandler;
|
|
sigemptyset(&sigact.sa_mask);
|
|
sigact.sa_flags = 0;
|
|
sigaction(SIGINT, &sigact, NULL);
|
|
sigaction(SIGTERM, &sigact, NULL);
|
|
sigaction(SIGQUIT, &sigact, NULL);
|
|
sigaction(SIGPIPE, &sigact, NULL);
|
|
sigaction(SIGUSR1, &sigact, NULL);
|
|
sigaction(SIGINFO, &sigact, NULL);
|
|
#else
|
|
SetConsoleCtrlHandler((PHANDLER_ROUTINE)sighandler, TRUE);
|
|
#endif
|
|
/* Set the sample rate */
|
|
r = sdr_set_sample_rate(cfg.dev, cfg.samp_rate, 1); // always verbose
|
|
|
|
if (cfg.verbosity || demod->level_limit)
|
|
fprintf(stderr, "Bit detection level set to %d%s.\n", demod->level_limit, (demod->level_limit ? "" : " (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)
|
|
fprintf(stderr, "WARNING: Failed to reset buffers.\n");
|
|
r = sdr_activate(cfg.dev);
|
|
|
|
if (cfg.frequencies == 0) {
|
|
cfg.frequency[0] = DEFAULT_FREQUENCY;
|
|
cfg.frequencies = 1;
|
|
}
|
|
if (cfg.frequencies > 1 && cfg.hop_times == 0) {
|
|
cfg.hop_time[cfg.hop_times++] = DEFAULT_HOP_TIME;
|
|
}
|
|
if (cfg.verbosity) {
|
|
fprintf(stderr, "Reading samples in async mode...\n");
|
|
}
|
|
if (cfg.duration > 0) {
|
|
time(&cfg.stop_time);
|
|
cfg.stop_time += cfg.duration;
|
|
}
|
|
|
|
uint32_t samp_rate = cfg.samp_rate;
|
|
while (!cfg.do_exit) {
|
|
time(&cfg.hop_start_time);
|
|
|
|
/* Set the cfg.frequency */
|
|
cfg.center_frequency = cfg.frequency[cfg.frequency_index];
|
|
r = sdr_set_center_freq(cfg.dev, cfg.center_frequency, 1); // always verbose
|
|
|
|
if (samp_rate != cfg.samp_rate) {
|
|
r = sdr_set_sample_rate(cfg.dev, cfg.samp_rate, 1); // always verbose
|
|
update_protocols(&cfg);
|
|
samp_rate = cfg.samp_rate;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
signal(SIGALRM, sighandler);
|
|
alarm(3); // require callback to run every 3 second, abort otherwise
|
|
#endif
|
|
r = sdr_start(cfg.dev, sdr_callback, (void *)&cfg,
|
|
DEFAULT_ASYNC_BUF_NUMBER, cfg.out_block_size);
|
|
if (r < 0) {
|
|
fprintf(stderr, "WARNING: async read failed (%i).\n", r);
|
|
break;
|
|
}
|
|
#ifndef _WIN32
|
|
alarm(0); // cancel the watchdog timer
|
|
#endif
|
|
cfg.do_exit_async = 0;
|
|
cfg.frequency_index = (cfg.frequency_index + 1) % cfg.frequencies;
|
|
}
|
|
|
|
if (cfg.report_stats > 0) {
|
|
event_occurred_handler(&cfg, create_report_data(&cfg, cfg.report_stats));
|
|
flush_report_data(&cfg);
|
|
}
|
|
|
|
if (!cfg.do_exit)
|
|
fprintf(stderr, "\nLibrary error %d, exiting...\n", r);
|
|
|
|
r_free_cfg(&cfg);
|
|
|
|
return r >= 0 ? r : -r;
|
|
}
|