From 2aaaa6e10ee46cdfdbe768eb30599085e18faedd Mon Sep 17 00:00:00 2001 From: "Martin Herren (HB9FXX)" Date: Sat, 5 Aug 2023 21:14:44 +0200 Subject: [PATCH] [rffft] Add -P option to parse params from SatDump and GQRX filenames Signed-off-by: Martin Herren (HB9FXX) --- Makefile.linux | 6 +- Makefile.osx | 6 +- README.md | 6 +- makefile | 6 +- rffft.c | 22 ++++- rffft_internal.c | 163 +++++++++++++++++++++++++++++++++++ rffft_internal.h | 23 +++++ tests/tests.c | 2 + tests/tests_rffft_internal.c | 76 ++++++++++++++++ tests/tests_rffft_internal.h | 14 +++ 10 files changed, 311 insertions(+), 13 deletions(-) create mode 100644 rffft_internal.c create mode 100644 rffft_internal.h create mode 100644 tests/tests_rffft_internal.c create mode 100644 tests/tests_rffft_internal.h diff --git a/Makefile.linux b/Makefile.linux index 4a69479..b995e29 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -34,10 +34,10 @@ rftrack: rftrack.o rfio.o rftime.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rfplot: rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftles.o gfortran -o rfplot rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftles.o $(LFLAGS) -rffft: rffft.o rftime.o - $(CC) -o rffft rffft.o rftime.o -lfftw3f -lm +rffft: rffft.o rffft_internal.o rftime.o + $(CC) -o rffft rffft.o rffft_internal.o rftime.o -lfftw3f -lm -tests/tests: tests/tests.o tests/tests_rftles.o rftles.o satutl.o ferror.o +tests/tests: tests/tests.o tests/tests_rffft_internal.o tests/tests_rftles.o rffft_internal.o rftles.o satutl.o ferror.o $(CC) -Wall -o $@ $^ -lcmocka -lm tests: tests/tests diff --git a/Makefile.osx b/Makefile.osx index 9c5b26a..283e79d 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -48,10 +48,10 @@ rftrack: rftrack.o rfio.o rftime.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rfplot: rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o versafit.o dsmin.o simplex.o rftles.o $(CC) -o rfplot rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o versafit.o dsmin.o simplex.o rftles.o $(LFLAGS) -rffft: rffft.o rftime.o - $(CC) -o rffft rffft.o rftime.o -lfftw3f -lm $(LFLAGS) +rffft: rffft.o rffft_internal.o rftime.o + $(CC) -o rffft rffft.o rffft_internal.o rftime.o -lfftw3f -lm $(LFLAGS) -tests/tests: tests/tests.o tests/tests_rftles.o rftles.o satutl.o ferror.o +tests/tests: tests/tests.o tests/tests_rffft_internal.o tests/tests_rftles.o rffft_internal.o rftles.o satutl.o ferror.o $(CC) -Wall -o $@ $^ -lcmocka -lm tests: tests/tests diff --git a/README.md b/README.md index 9780145..417c85a 100644 --- a/README.md +++ b/README.md @@ -81,4 +81,8 @@ With I/Q recordings obtained from Gqrx: **Gqrx** records complex samples into `raw` files. The filename contains date, time, center frequency and samplerate separated by underscores. Replace `YYYYMMDD` and `HHMMSS` by your actual time and respectively. Pay attention to insert an uppercase `T` between date and time in the time stamp parameter of the `rffft` command. -The output spectrograms can be viewed and analysed using `rfplot`. +Alternatively, with I/Q recordings from GQRX and SatDump, the `-P` option can be used to automatically extract the timestamp, format, frequency and samplerate from the filename: + + ./rffft -P -i gqrx_YYYYMMDD_HHMMSS_97400000_2000000_fc.raw + +The output spectrograms can be viewed and analysed using `rfplot`. diff --git a/makefile b/makefile index f3b74e8..1292155 100644 --- a/makefile +++ b/makefile @@ -37,10 +37,10 @@ rftrack: rftrack.o rfio.o rftime.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rfplot: rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o versafit.o dsmin.o simplex.o rftles.o gfortran -o rfplot rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o versafit.o dsmin.o simplex.o rftles.o $(LFLAGS) -rffft: rffft.o rftime.o - $(CC) -o rffft rffft.o rftime.o -lfftw3f -lm +rffft: rffft.o rffft_internal.o rftime.o + $(CC) -o rffft rffft.o rffft_internal.o rftime.o -lfftw3f -lm -tests/tests: tests/tests.o tests/tests_rftles.o rftles.o satutl.o ferror.o +tests/tests: tests/tests.o tests/tests_rffft_internal.o tests/tests_rftles.o rffft_internal.o rftles.o satutl.o ferror.o $(CC) -Wall -o $@ $^ -lcmocka -lm tests: tests/tests diff --git a/rffft.c b/rffft.c index 8f0ebf7..6eee58f 100644 --- a/rffft.c +++ b/rffft.c @@ -8,6 +8,8 @@ #include #include "rftime.h" +#include "rffft_internal.h" + void usage(void) { printf("rffft: FFT RF observations\n\n"); @@ -27,6 +29,7 @@ void usage(void) printf("-I Invert frequencies\n"); printf("-b Digitize output to bytes [off]\n"); printf("-q Quiet mode, no output [off]\n"); + printf("-P Parse frequency, samplerate, format and start time from filename\n"); printf("-h This help\n"); return; @@ -49,10 +52,11 @@ int main(int argc,char *argv[]) struct timeval start,end; char tbuf[30],nfd[32],header[256]=""; int sign=1; + int parse_params_from_filename = 0; // Read arguments if (argc>1) { - while ((arg=getopt(argc,argv,"i:f:s:c:t:p:n:hm:F:T:bqR:o:IS:"))!=-1) { + while ((arg=getopt(argc,argv,"i:f:s:c:t:p:n:hm:F:T:bqR:o:IS:P"))!=-1) { switch(arg) { case 'i': @@ -125,7 +129,12 @@ int main(int argc,char *argv[]) case 'I': sign=-1; break; - + + case 'P': + parse_params_from_filename = 1; + realtime=0; + break; + case 'h': usage(); return 0; @@ -140,9 +149,16 @@ int main(int argc,char *argv[]) return 0; } + if (parse_params_from_filename != 0) { + if (rffft_params_from_filename(infname, &samp_rate, &freq, &informat, nfd) != 0) { + fprintf(stderr, "Error parsing parameters from filename\n"); + exit(-1); + }; + } + // Ensure integer number of spectra per subintegration tint=ceil(fchan*tint)/fchan; - + // Number of channels nchan=(int) (samp_rate/fchan); diff --git a/rffft_internal.c b/rffft_internal.c new file mode 100644 index 0000000..4690d3d --- /dev/null +++ b/rffft_internal.c @@ -0,0 +1,163 @@ +#include "rffft_internal.h" + +#include +#include +#include +#include + + +// Filename formats: +// - SatDump +// - 2023-08-05_08-02-00_16000000SPS_2274000000Hz.s8 +// - 2023-08-05_18-02-45-534_16000000SPS_2284000000Hz.s16 +// - 2023-08-05_18-02-45-1691258565.534000_16000000SPS_2284000000Hz.f32 +// s8: char, s16 short int, f32 float. +// SatDump also supports .wav and compressed versions of s8/s16/f32 with .ziq +// extension. Those are not yet supported +// timestamp can have an added milliseconds field, configurable. This feature +// was broken during some time so files with this convention still exists. +// - GQRX: +// - gqrx_20230806_151838_428000000_200000_fc.raw +// format always float32 +int rffft_params_from_filename(char * filename, double * samplerate, double * frequency, char * format, char * starttime) { + // Temp vars to hold parsed values + int p_year, p_month, p_day, p_hours, p_minutes, p_seconds, p_fractal_seconds; + int p_dummy_int; + double p_samplerate, p_frequency; + char p_format[16]; + char p_dummy_string[128]; + int parsed_tokens; + + char * base_filename = basename(filename); + + // Broken SatDump string with milliseconds activated + parsed_tokens = sscanf( + base_filename, + "%04d-%02d-%02d_%02d-%02d-%02d-%d.%06d_%lfSPS_%lfHz.%s", + &p_year, + &p_month, + &p_day, + &p_hours, + &p_minutes, + &p_seconds, + &p_dummy_int, + &p_fractal_seconds, + &p_samplerate, + &p_frequency, + p_format); + + if (parsed_tokens == 11) { + *samplerate = p_samplerate; + *frequency = p_frequency; + + if ((strlen(p_format) == 2) && (strncmp("s8", p_format, 2) == 0)) { + *format = 'c'; + } else if ((strlen(p_format) == 3) && (strncmp("s16", p_format, 3) == 0)) { + *format = 'i'; + } else if ((strlen(p_format) == 3) && (strncmp("f32", p_format, 3) == 0)) { + *format = 'f'; + } else { + printf("Unsupported SatDump format %s\n", p_format); + return -1; + } + + snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", p_year, p_month, p_day, p_hours, p_minutes, p_seconds, p_fractal_seconds / 1000); + + return 0; + } + + // SatDump string with milliseconds activated + parsed_tokens = sscanf( + base_filename, + "%04d-%02d-%02d_%02d-%02d-%02d-%03d_%lfSPS_%lfHz.%s", + &p_year, + &p_month, + &p_day, + &p_hours, + &p_minutes, + &p_seconds, + &p_fractal_seconds, + &p_samplerate, + &p_frequency, + p_format); + + if (parsed_tokens == 10) { + *samplerate = p_samplerate; + *frequency = p_frequency; + + if ((strlen(p_format) == 2) && (strncmp("s8", p_format, 2) == 0)) { + *format = 'c'; + } else if ((strlen(p_format) == 3) && (strncmp("s16", p_format, 3) == 0)) { + *format = 'i'; + } else if ((strlen(p_format) == 3) && (strncmp("f32", p_format, 3) == 0)) { + *format = 'f'; + } else { + printf("Unsupported SatDump format %s\n", p_format); + return -1; + } + + snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", p_year, p_month, p_day, p_hours, p_minutes, p_seconds, p_fractal_seconds); + + return 0; + } + + // SatDump string without milliseconds activated + parsed_tokens = sscanf( + base_filename, + "%04d-%02d-%02d_%02d-%02d-%02d_%lfSPS_%lfHz.%s", + &p_year, + &p_month, + &p_day, + &p_hours, + &p_minutes, + &p_seconds, + &p_samplerate, + &p_frequency, + p_format); + + if (parsed_tokens == 9) { + *samplerate = p_samplerate; + *frequency = p_frequency; + + if ((strlen(p_format) == 2) && (strncmp("s8", p_format, 2) == 0)) { + *format = 'c'; + } else if ((strlen(p_format) == 3) && (strncmp("s16", p_format, 3) == 0)) { + *format = 'i'; + } else if ((strlen(p_format) == 3) && (strncmp("f32", p_format, 3) == 0)) { + *format = 'f'; + } else { + printf("Unsupported SatDump format %s\n", p_format); + return -1; + } + + snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d", p_year, p_month, p_day, p_hours, p_minutes, p_seconds); + + return 0; + } + + // GQRX + parsed_tokens = sscanf( + base_filename, + "gqrx_%04d%02d%02d_%02d%02d%02d_%lf_%lf_%s.raw", + &p_year, + &p_month, + &p_day, + &p_hours, + &p_minutes, + &p_seconds, + &p_frequency, + &p_samplerate, + p_dummy_string); + + if (parsed_tokens == 9) { + *samplerate = p_samplerate; + *frequency = p_frequency; + *format = 'f'; + + snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d", p_year, p_month, p_day, p_hours, p_minutes, p_seconds); + + return 0; + } + + return -1; +} diff --git a/rffft_internal.h b/rffft_internal.h new file mode 100644 index 0000000..7eca88b --- /dev/null +++ b/rffft_internal.h @@ -0,0 +1,23 @@ +#ifndef _RFFFT_INTERNAL_H +#define _RFFFT_INTERNAL_H + +#include "sgdp4h.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// input: +// filename: filename string to parse +// output: +// samplerate: parsed samplerate +// frequency: parsed frequency +// format: parsed sample format: char: 'c', int: 'i', float: 'f' +// starttime: parsed start time string formatted YYYY-MM-DDTHH:MM:SS.sss +int rffft_params_from_filename(char * filename, double * samplerate, double * frequency, char * format, char * starttime); + +#ifdef __cplusplus +} +#endif + +#endif /* _RFFFT_INTERNAL_H */ diff --git a/tests/tests.c b/tests/tests.c index 5e6094a..2d1c5d9 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -1,3 +1,4 @@ +#include "tests_rffft_internal.h" #include "tests_rftles.h" #include @@ -9,6 +10,7 @@ int main(void) { int failures = 0; + failures += run_rffft_internal_tests(); failures += run_tle_tests(); return failures; diff --git a/tests/tests_rffft_internal.c b/tests/tests_rffft_internal.c new file mode 100644 index 0000000..4b17732 --- /dev/null +++ b/tests/tests_rffft_internal.c @@ -0,0 +1,76 @@ +#include "tests_rffft_internal.h" + +#include +#include +#include +#include +#include + +#include "../rffft_internal.h" + +// Tests + +// Test SatDump filenames +void rffft_internal_parse_satdump_filenames(void **state) { + double samplerate = 0; + double frequency = 0; + char format = '\0'; + char starttime[] = "YYYY-mm-ddTHH:MM:SS.sss"; + char ref_format = '\0'; + + // s8 file without milliseconds + ref_format = 'c'; + assert_int_equal(0, rffft_params_from_filename("2023-08-05_08-02-00_16000000SPS_2274000000Hz.s8", &samplerate, &frequency, &format, starttime)); + // assert_double_equal has been introduced in cmocka 1.1.6 not available on most distribs yet + assert_float_equal(16e6, samplerate, 1e-12); + assert_float_equal(2.274e9, frequency, 1e-12); + assert_memory_equal(&ref_format, &format, 1); + assert_string_equal("2023-08-05T08:02:00", starttime); + + // f32 file with milliseconds + ref_format = 'f'; + assert_int_equal(0, rffft_params_from_filename("2023-08-17_11-41-14-373_1000000SPS_100000000Hz.f32", &samplerate, &frequency, &format, starttime)); + assert_float_equal(1e6, samplerate, 1e-12); + assert_float_equal(100e6, frequency, 1e-12); + assert_memory_equal(&ref_format, &format, 1); + assert_string_equal("2023-08-17T11:41:14.373", starttime); + + // s16 file with broken milliseconds format + ref_format = 'i'; + assert_int_equal(0, rffft_params_from_filename("2023-08-05_18-02-45-1691258565.534000_8000000SPS_2284000000Hz.s16", &samplerate, &frequency, &format, starttime)); + assert_float_equal(8e6, samplerate, 1e-12); + assert_float_equal(2.284e9, frequency, 1e-12); + assert_memory_equal(&ref_format, &format, 1); + assert_string_equal("2023-08-05T18:02:45.534", starttime); + + assert_int_equal(-1, rffft_params_from_filename("2023-08-05-19:59:30_16000000SPS_402000000Hz.f32", &samplerate, &frequency, &format, starttime)); +} + +// Test GQRX filenames +void rffft_internal_parse_gqrx_filenames(void **state) { + double samplerate = 0; + double frequency = 0; + char format = '\0'; + char starttime[] = "YYYY-mm-ddTHH:MM:SS.sss"; + char ref_format = '\0'; + + ref_format = 'f'; + assert_int_equal(0, rffft_params_from_filename("gqrx_20230806_151838_428000000_200000_fc.raw", &samplerate, &frequency, &format, starttime)); + // assert_double_equal has been introduced in cmocka 1.1.6 not available on most distribs yet + assert_float_equal(200e3, samplerate, 1e-12); + assert_float_equal(428e6, frequency, 1e-12); + assert_memory_equal(&ref_format, &format, 1); + assert_string_equal("2023-08-06T15:18:38", starttime); + + assert_int_equal(-1, rffft_params_from_filename("gqrx_2023-08-06_15:18:38_428000000_200000_fc.raw", &samplerate, &frequency, &format, starttime)); +} + +// Entry point to run all tests +int run_rffft_internal_tests() { + const struct CMUnitTest tests[] = { + cmocka_unit_test(rffft_internal_parse_satdump_filenames), + cmocka_unit_test(rffft_internal_parse_gqrx_filenames), + }; + + return cmocka_run_group_tests_name("rffft internal", tests, NULL, NULL); +} diff --git a/tests/tests_rffft_internal.h b/tests/tests_rffft_internal.h new file mode 100644 index 0000000..29648d6 --- /dev/null +++ b/tests/tests_rffft_internal.h @@ -0,0 +1,14 @@ +#ifndef _TESTS_RFFFT_INTERNAL_H +#define _TESTS_RFFFT_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +int run_rffft_internal_tests(); + +#ifdef __cplusplus +} +#endif + +#endif /* _TESTS_RFFFT_INTERNAL_H */