From 0c7c13f609777e2d6a4d05849a668273a7e5d525 Mon Sep 17 00:00:00 2001 From: Martin Herren Date: Thu, 9 Feb 2023 21:40:20 +0100 Subject: [PATCH 1/4] [Tests] Add initial unit tests on rftles based on cmocka framework Signed-off-by: Martin Herren (HB9FXX) --- Makefile.linux | 10 ++- Makefile.osx | 10 ++- README.md | 1 + makefile | 10 ++- rftles.c | 10 ++- tests/data/catalog.tle | 135 ++++++++++++++++++++++++++++++++ tests/data/empty.tle | 0 tests/tests.c | 15 ++++ tests/tests_rftles.c | 171 +++++++++++++++++++++++++++++++++++++++++ tests/tests_rftles.h | 14 ++++ 10 files changed, 369 insertions(+), 7 deletions(-) create mode 100644 tests/data/catalog.tle create mode 100644 tests/data/empty.tle create mode 100644 tests/tests.c create mode 100644 tests/tests_rftles.c create mode 100644 tests/tests_rftles.h diff --git a/Makefile.linux b/Makefile.linux index 9f95c1c..4a69479 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -37,10 +37,16 @@ rfplot: rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftl rffft: rffft.o rftime.o $(CC) -o rffft rffft.o rftime.o -lfftw3f -lm -.PHONY: clean install uninstall +tests/tests: tests/tests.o tests/tests_rftles.o rftles.o satutl.o ferror.o + $(CC) -Wall -o $@ $^ -lcmocka -lm + +tests: tests/tests + ./tests/tests + +.PHONY: clean install uninstall tests clean: - rm -f *.o + rm -f *.o tests/*.o rm -f *~ install: diff --git a/Makefile.osx b/Makefile.osx index aa8edf5..9c5b26a 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -51,10 +51,16 @@ rfplot: rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o vers rffft: rffft.o rftime.o $(CC) -o rffft rffft.o rftime.o -lfftw3f -lm $(LFLAGS) -.PHONY: clean install uninstall +tests/tests: tests/tests.o tests/tests_rftles.o rftles.o satutl.o ferror.o + $(CC) -Wall -o $@ $^ -lcmocka -lm + +tests: tests/tests + ./tests/tests + +.PHONY: clean install uninstall tests clean: - rm -f *.o + rm -f *.o tests/*.o rm -f *~ install: diff --git a/README.md b/README.md index e6f464d..9780145 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Install * Clone repository: `git clone https://github.com/cbassa/strf.git` * Compile: `cd strf; make` * Install (in `/usr/local`): `sudo make install` + * To build and run the unit tests, also install the cmocka dependencies: `sudo apt install libcmocka-dev libcmocka0` and then run `make tests` Configure --------- diff --git a/makefile b/makefile index cd4fbc4..f3b74e8 100644 --- a/makefile +++ b/makefile @@ -40,10 +40,16 @@ rfplot: rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o vers rffft: rffft.o rftime.o $(CC) -o rffft rffft.o rftime.o -lfftw3f -lm -.PHONY: clean install uninstall +tests/tests: tests/tests.o tests/tests_rftles.o rftles.o satutl.o ferror.o + $(CC) -Wall -o $@ $^ -lcmocka -lm + +tests: tests/tests + ./tests/tests + +.PHONY: clean install uninstall tests clean: - rm -f *.o + rm -f *.o tests/*.o rm -f *~ install: diff --git a/rftles.c b/rftles.c index 59a7cb4..43e2827 100644 --- a/rftles.c +++ b/rftles.c @@ -47,6 +47,14 @@ tles_t load_tles(char *tlefile) { } } + // Don't allocate anything if no entry found in file + if (num_elements == 0) { + fclose(file); + free(line); + + return tles; + } + tles.orbits = (orbit_t *)calloc(num_elements, sizeof(orbit_t)); // Rewind and parse file @@ -72,7 +80,7 @@ void free_tles(tles_t *tles) { } orbit_t *get_orbit_by_index(tles_t *tles, long index) { - if (tles) { + if (tles && (index < tles->number_of_elements)) { return &(tles->orbits[index]); } diff --git a/tests/data/catalog.tle b/tests/data/catalog.tle new file mode 100644 index 0000000..6200901 --- /dev/null +++ b/tests/data/catalog.tle @@ -0,0 +1,135 @@ +0 LEMUR 2 KAREN_B +1 52736U 22057E 23036.86389985 .00017304 00000-0 82793-3 0 9996 +2 52736 97.5231 153.7414 0006358 83.3132 276.8826 15.19025879 38769 +0 GHGSAT-C3 +1 52737U 22057F 23037.09204999 .00003459 00000-0 19726-3 0 9996 +2 52737 97.5315 153.4851 0010090 90.2911 83.7555 15.13397267 38744 +0 PLANETUM1 +1 52738U 22057G 23037.05354110 .00012014 00000-0 63489-3 0 9996 +2 52738 97.5267 153.6138 0008282 84.3113 275.9064 15.15611464 38761 +0 CONNECTA T1-1 +1 52739U 22057H 23036.88769218 .00006966 00000-0 38624-3 0 9998 +2 52739 97.5358 153.5145 0010190 89.9510 270.2889 15.14050840 38726 +0 LEMUR 2 HANCOM-1 +1 52740U 22057J 23037.08570718 .00017932 00000-0 90182-3 0 9990 +2 52740 97.5413 154.4138 0010408 91.3570 268.8856 15.17248891 38788 +0 OP15 FLT1 (TYVAK-0820) +1 52741U 22057K 23036.85086563 .00013598 00000-0 69859-3 0 9996 +2 52741 97.5364 153.9211 0009814 88.4347 271.8010 15.16566713 38745 +0 CENTAURI 5 (TYVAK-0212) +1 52742U 22057L 23037.09912698 .00020032 00000-0 98481-3 0 9995 +2 52742 97.5279 154.1026 0007435 85.2751 274.9332 15.18041987 38798 +0 OBJECT M +1 52743U 22057M 23037.04954473 .00011781 00000-0 61944-3 0 9993 +2 52743 97.5265 153.6940 0008594 82.9904 31.3082 15.15793680 38769 +0 GHGSAT-C5 +1 52744U 22057N 23037.07272071 .00003967 00000-0 22555-3 0 9996 +2 52744 97.5377 153.7350 0011367 88.3414 86.3529 15.13411014 38748 +0 AMS +1 52745U 22057P 23036.90397027 .00042985 00000-0 15803-2 0 9997 +2 52745 97.5123 155.7248 0022421 114.7010 245.6566 15.27525761 38904 +0 GHGSAT-C4 +1 52746U 22057Q 23037.10750856 .00003860 00000-0 21930-3 0 9992 +2 52746 97.5251 153.3935 0010354 79.3126 23.4440 15.13459557 38750 +0 NUSAT-28 (ALICE LEE) +1 52747U 22057R 23037.22603551 .00011634 00000-0 53078-3 0 9991 +2 52747 97.4775 154.1756 0010454 54.6631 305.5580 15.20730797 38901 +0 NUSAT-30 (MARGHERITA) +1 52748U 22057S 23037.06296123 .00014923 00000-0 56759-3 0 9990 +2 52748 97.3595 153.1456 0008136 29.6606 330.5093 15.26838154 39039 +0 ICEYE-X18 +1 52749U 22057T 23037.11421501 .00016269 00000-0 85173-3 0 9994 +2 52749 97.5277 153.8566 0009622 50.8929 75.3110 15.15861349 38777 +0 FOSSASAT2E11 +1 52750U 22057U 23037.09183814 .00042572 00000-0 17460-2 0 9994 +2 52750 97.5329 155.3908 0008343 63.2717 296.9373 15.24088376 38869 +0 OBJECT V +1 52751U 22057V 23037.14037215 .00004553 00000-0 25511-3 0 9998 +2 52751 97.5273 153.5975 0010597 75.1988 46.5731 15.13861159 38760 +0 NUSAT-31 (RUBY PAYNE-S) +1 52752U 22057W 23037.21796854 .00009140 00000-0 48828-3 0 9991 +2 52752 97.5329 154.0609 0010266 74.5046 285.7319 15.15284975 38793 +0 UMBRA-03 +1 52753U 22057X 23036.89099654 .00024550 00000-0 13714-2 0 9996 +2 52753 97.5273 152.8545 0003373 356.2312 3.8893 15.13497837 38690 +0 HAWK-5C +1 52754U 22057Y 23037.12809845 .00006319 00000-0 34779-3 0 9993 +2 52754 97.5417 154.1811 0010579 77.8956 282.3461 15.14348679 38772 +0 ICEYE-X24 +1 52755U 22057Z 23037.10645816 .00017128 00000-0 85326-3 0 9992 +2 52755 97.5296 154.2550 0008875 73.5191 98.9679 15.17603716 38792 +0 HAWK-5B +1 52756U 22057AA 23036.86364832 .00005988 00000-0 32977-3 0 9996 +2 52756 97.5264 153.3553 0011256 73.7736 286.4733 15.14345711 38739 +0 HAWK-5A +1 52757U 22057AB 23037.12774664 .00006166 00000-0 33934-3 0 9998 +2 52757 97.5340 153.8973 0011616 76.3222 283.9302 15.14350099 38778 +0 ICEYE-X19 +1 52758U 22057AC 23037.14353186 .00015866 00000-0 83148-3 0 9992 +2 52758 97.5303 154.0439 0013188 54.2839 45.1844 15.15795109 38789 +0 ICEYE-X20 +1 52759U 22057AD 23037.12140059 .00016162 00000-0 80321-3 0 9994 +2 52759 97.5299 154.3640 0010407 71.3635 31.3468 15.17687465 38805 +0 VIGORIDE-3 +1 52760U 22057AE 23036.88124168 .00003100 00000-0 17516-3 0 9995 +2 52760 97.5324 153.5507 0012882 73.3749 286.8896 15.13781195 38731 +0 ION SCV-006 +1 52761U 22057AF 23037.04836934 .00004178 00000-0 23221-3 0 9992 +2 52761 97.5336 153.8286 0013112 72.9702 287.2965 15.14173716 38763 +0 ICEYE-X17 +1 52762U 22057AG 23037.11094679 .00015553 00000-0 77289-3 0 9995 +2 52762 97.5302 154.3803 0010731 72.0978 30.7629 15.17695768 38800 +0 SHERPA-AC1 +1 52763U 22057AH 23036.89429988 .00003765 00000-0 20894-3 0 9997 +2 52763 97.5338 153.7409 0013220 71.3804 288.8862 15.14290249 38741 +0 NUSAT-29 (EDITH CLARKE) +1 52764U 22057AJ 23037.25786775 .00012139 00000-0 55246-3 0 9993 +2 52764 97.4805 154.3300 0012460 44.8157 315.4082 15.20784362 38919 +0 ARMSAT1 URDANETA +1 52765U 22057AK 23037.14532730 .00009541 00000-0 49292-3 0 9999 +2 52765 97.5348 154.4763 0012917 64.8353 56.2978 15.16433317 38802 +0 CELESTIS 21 - VARISAT 1C +1 52766U 22057AL 23037.03165535 .00008255 00000-0 43133-3 0 9992 +2 52766 97.5363 154.3387 0013689 67.6542 50.0596 15.16067844 38782 +0 CNCE4 +1 52767U 22057AM 23036.85668304 .00020245 00000-0 95027-3 0 9996 +2 52767 97.5277 154.4023 0011115 62.0389 298.1970 15.19619463 38790 +0 CNCE5 +1 52768U 22057AN 23036.72497729 .00019899 00000-0 93485-3 0 9993 +2 52768 97.5280 154.2725 0011004 63.3387 296.8973 15.19593235 38775 +0 LEMUR 2 MIMI1307 +1 52769U 22057AP 23036.89557315 .00006598 00000-0 32449-3 0 9995 +2 52769 97.5403 154.6093 0013610 69.1358 291.1332 15.18277619 38784 +0 PLATFORM-1 +1 52770U 22057AQ 23036.83446388 .00005396 00000-0 28018-3 0 9990 +2 52770 97.5303 154.0147 0012921 58.9624 301.2876 15.16466316 38761 +0 OMNI-L2 +1 52771U 22057AR 23036.60749533 .00008836 00000-0 59024-3 0 9997 +2 52771 97.5247 153.3085 0011966 66.5366 293.7118 15.07172161 38696 +0 CPOD FLT1 (TYVAK-0032) +1 52772U 22057AS 23037.06888833 .00019927 00000-0 97173-3 0 9996 +2 52772 97.5287 154.5002 0012822 54.2533 305.9891 15.18278587 38814 +0 FOSSASAT2E12 +1 52773U 22057AT 23036.82423510 .00030322 00000-0 12979-2 0 9999 +2 52773 97.5324 154.9702 0009451 65.8451 294.3773 15.22710138 38817 +0 SBUDNIC +1 52774U 22057AU 23036.87572409 .00049210 00000-0 19067-2 0 9998 +2 52774 97.5283 155.3625 0004254 48.3756 311.7846 15.25980096 38844 +0 GUARDIAN +1 52775U 22057AV 23037.13644854 .00007718 00000-0 42378-3 0 9992 +2 52775 97.5302 153.7201 0010532 88.8794 33.4406 15.14361229 38755 +0 FOSSASAT2E13 +1 52776U 22057AW 23036.85686868 .00018369 00000-0 86958-3 0 9998 +2 52776 97.5320 154.3372 0012965 61.4900 298.7638 15.19324828 38785 +0 VEERY-FS1 +1 52777U 22057AX 23037.05822551 .00020001 00000-0 94866-3 0 9996 +2 52777 97.5315 154.5562 0013712 58.7801 301.4775 15.19231505 38810 +0 FOSSASAT2E7 +1 52778U 22057AY 23036.88096332 .00040630 00000-0 17406-2 0 9991 +2 52778 97.5284 154.4416 0009517 69.0081 291.2172 15.22613958 38778 +0 FOSSASAT2E8 +1 52779U 22057AZ 23036.83569229 .00029862 00000-0 13576-2 0 9995 +2 52779 97.5298 154.2065 0010443 72.4751 287.7624 15.20644843 38760 +0 CPOD FLT2 (TYVAK-0033) +1 52780U 22057BB 23036.86744141 .00018086 00000-0 87869-3 0 9991 +2 52780 97.5313 154.3283 0011660 53.1934 307.0368 15.18441019 16465 diff --git a/tests/data/empty.tle b/tests/data/empty.tle new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests.c b/tests/tests.c new file mode 100644 index 0000000..5e6094a --- /dev/null +++ b/tests/tests.c @@ -0,0 +1,15 @@ +#include "tests_rftles.h" + +#include +#include +#include +#include +#include + +int main(void) { + int failures = 0; + + failures += run_tle_tests(); + + return failures; +} diff --git a/tests/tests_rftles.c b/tests/tests_rftles.c new file mode 100644 index 0000000..83d6b49 --- /dev/null +++ b/tests/tests_rftles.c @@ -0,0 +1,171 @@ +#include "tests_rftles.h" + +#include +#include +#include +#include +#include + +#include "../rftles.h" + +// Helpers functions for setup/teardown +void *tle_load(char *filename) { + tles_t *tles = malloc(sizeof(tles_t)); + + if (tles == NULL) { + return NULL; + } + + *tles = load_tles(filename); + + return tles; +} + +void tle_free(void *tles) { + free_tles(tles); + free(tles); +} + +// Setup and teardown functions for tests +int setup_nonexistent(void **state) { + tles_t * tles = tle_load("tests/data/nonexistent.tle"); + + if (tles == NULL) { + return -1; + } + + *state = tles; + + return 0; +} + +int setup_empty(void **state) { + tles_t * tles = tle_load("tests/data/empty.tle"); + + if (tles == NULL) { + return -1; + } + + *state = tles; + + return 0; +} + +int setup(void **state) { + tles_t * tles = tle_load("tests/data/catalog.tle"); + + if (tles == NULL) { + return -1; + } + + *state = tles; + + return 0; +} + +int teardown(void **state) { + tle_free(*state); + + return 0; +} + +// Tests +void TLE_load_nonexistent_file(void **state) { + tles_t tles = **(tles_t **)state; + + assert_null(tles.orbits); + assert_int_equal(tles.number_of_elements, 0); +} + +void TLE_load_empty_file(void **state) { + tles_t tles = **(tles_t **)state; + + assert_null(tles.orbits); + assert_int_equal(tles.number_of_elements, 0); +} + +void TLE_load_invalid_index_from_file(void **state) { + tles_t tles = **(tles_t **)state; + + assert_non_null(tles.orbits); + assert_int_equal(tles.number_of_elements, 45); + + orbit_t * orbit = get_orbit_by_index(&tles, 46); + assert_null(orbit); +} + +void TLE_load_index_from_file(void **state) { + tles_t tles = **(tles_t **)state; + + assert_non_null(tles.orbits); + assert_int_equal(tles.number_of_elements, 45); + + // AMS + orbit_t * orbit = get_orbit_by_index(&tles, 9); + assert_int_equal(orbit->ep_year, 2023); + assert_float_equal(orbit->ep_day, 36.90397027, 1e-9); + assert_float_equal(orbit->rev, 15.27525761, 1e-9); + assert_float_equal(orbit->bstar, 0.0015803, 1e-9); + assert_float_equal(orbit->eqinc, 1.701910696, 1e-9); + assert_float_equal(orbit->ecc, 0.0022421, 1e-9); + assert_float_equal(orbit->mnan, 4.287516499, 1e-9); + assert_float_equal(orbit->argp, 2.001910105, 1e-9); + assert_float_equal(orbit->ascn, 2.717910487, 1e-9); + // smjaxs is not set by sgdp4h + assert_float_equal(orbit->smjaxs, 0., 1e-9); + assert_float_equal(orbit->ndot2, 0.00042985, 1e-9); + assert_float_equal(orbit->nddot6, 0., 1e-9); + assert_string_equal(orbit->desig, "22057P"); + assert_int_equal(orbit->norb, 3890); + assert_int_equal(orbit->satno, 52745); +} + +void TLE_load_invalid_catalog_id_from_file(void **state) { + tles_t tles = **(tles_t **)state; + + assert_non_null(tles.orbits); + assert_int_equal(tles.number_of_elements, 45); + + orbit_t * orbit = get_orbit_by_catalog_id(&tles, 12000); + assert_null(orbit); +} + +void TLE_load_catalog_id_from_file(void **state) { + tles_t tles = **(tles_t **)state; + + assert_non_null(tles.orbits); + assert_int_equal(tles.number_of_elements, 45); + + // ICEYE-X18 + orbit_t * orbit = get_orbit_by_catalog_id(&tles, 52749); + assert_int_equal(orbit->ep_year, 2023); + assert_float_equal(orbit->ep_day, 37.11421501, 1e-9); + assert_float_equal(orbit->rev, 15.15861349, 1e-9); + assert_float_equal(orbit->bstar, 0.00085173, 1e-9); + assert_float_equal(orbit->eqinc, 1.702179477, 1e-9); + assert_float_equal(orbit->ecc, 0.0009622, 1e-9); + assert_float_equal(orbit->mnan, 1.314424913, 1e-9); + assert_float_equal(orbit->argp, 0.888248671, 1e-9); + assert_float_equal(orbit->ascn, 2.685304246, 1e-9); + // smjaxs is not set by sgdp4h + assert_float_equal(orbit->smjaxs, 0., 1e-9); + assert_float_equal(orbit->ndot2, 0.00016269, 1e-9); + assert_float_equal(orbit->nddot6, 0., 1e-9); + assert_string_equal(orbit->desig, "22057T"); + assert_int_equal(orbit->norb, 3877); + assert_int_equal(orbit->satno, 52749); +} + +// Entry point to run all tests +int run_tle_tests() { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(TLE_load_nonexistent_file, setup_nonexistent, teardown), + cmocka_unit_test_setup_teardown(TLE_load_empty_file, setup_empty, teardown), + cmocka_unit_test_setup_teardown(TLE_load_invalid_index_from_file, setup, teardown), + cmocka_unit_test_setup_teardown(TLE_load_index_from_file, setup, teardown), + cmocka_unit_test_setup_teardown(TLE_load_invalid_catalog_id_from_file, setup, teardown), + cmocka_unit_test_setup_teardown(TLE_load_catalog_id_from_file, setup, teardown), + }; + + return cmocka_run_group_tests_name("TLE", tests, NULL, NULL); +} diff --git a/tests/tests_rftles.h b/tests/tests_rftles.h new file mode 100644 index 0000000..0c3eb47 --- /dev/null +++ b/tests/tests_rftles.h @@ -0,0 +1,14 @@ +#ifndef _TESTS_RFTLES_H +#define _TESTS_RFTLES_H + +#ifdef __cplusplus +extern "C" { +#endif + +int run_tle_tests(); + +#ifdef __cplusplus +} +#endif + +#endif /* _TESTS_RFTLES_H */ From 351db5bf37a3c3e19df99e6b59274e400e93b5d0 Mon Sep 17 00:00:00 2001 From: "Martin Herren (HB9FXX)" Date: Tue, 28 Feb 2023 22:01:35 +0100 Subject: [PATCH 2/4] [Tests] Add github actions Signed-off-by: Martin Herren (HB9FXX) --- .github/workflows/github-actions.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/github-actions.yml diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..083db37 --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,13 @@ +name: GitHub Actions +run-name: Building and testing STRF +on: [push] +jobs: + GitHub-Actions: + runs-on: ubuntu-latest + steps: + - run: sudo apt install --no-install-recommends make gcc pgplot5 gfortran libpng-dev libx11-dev libgsl-dev libfftw3-dev libcmocka-dev libcmocka0 + - name: Check out repository code + uses: actions/checkout@v3 + - run: cd ${{ github.workspace }} + - run: make + - run: make tests From 96d4ac35e58ce85f8417a00f9d35d768d80b48ad Mon Sep 17 00:00:00 2001 From: "Martin Herren (HB9FXX)" Date: Wed, 15 Feb 2023 22:27:06 +0100 Subject: [PATCH 3/4] [SatUtl] Simplify read_twoline logic to make it more understandable Signed-off-by: Martin Herren (HB9FXX) --- satutl.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/satutl.c b/satutl.c index 1946712..e99876c 100644 --- a/satutl.c +++ b/satutl.c @@ -44,7 +44,7 @@ int read_twoline(FILE *fp, long search_satno, orbit_t *orb) static char line1[ST_SIZE]; static char line2[ST_SIZE]; char *st1, *st2; - int found; + int found = 0; double bm, bx; st1 = line1; @@ -54,17 +54,13 @@ int read_twoline(FILE *fp, long search_satno, orbit_t *orb) if(fgets(line1, ST_SIZE-1, fp) == NULL) return -1; st1 = st_start(line1); } while(st1[0] != '1'); - - if(search_satno > 0) - { - found = 0; - } - else - { - found = 1; - search_satno = atol(st1+2); - } - + + if (search_satno == 0) { + // If no search_satno given, set it to the currently read one + // so next do/while loop will find it + search_satno = atol(st1+2); + } + sprintf(search, "1 %05ld", search_satno); do { From 2aaaa6e10ee46cdfdbe768eb30599085e18faedd Mon Sep 17 00:00:00 2001 From: "Martin Herren (HB9FXX)" Date: Sat, 5 Aug 2023 21:14:44 +0200 Subject: [PATCH 4/4] [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 */