├── src ├── sgpsdp │ ├── TR │ │ ├── README │ │ ├── Makefile.am │ │ ├── test-001-01.res │ │ └── test-002-01.res │ ├── README │ ├── 1_COPYING │ ├── Makefile.am │ ├── solar.c │ └── sgp_math.c ├── math │ └── fast_atan2f.h ├── dsp │ ├── lpf_taps.h │ ├── gaussian_taps.h │ ├── dc_blocker.h │ ├── lpf.h │ ├── clock_recovery_mm.h │ ├── quadrature_demod.h │ ├── mmse_fir_interpolator.h │ ├── interp_fir_filter.h │ ├── frequency_modulator.h │ ├── fsk_demod.h │ ├── gfsk_mod.h │ ├── sig_source.h │ ├── doppler.h │ ├── gaussian_taps.c │ ├── fir_filter.h │ ├── lpf.c │ ├── frequency_modulator.c │ ├── sig_source.c │ ├── lpf_taps.c │ ├── quadrature_demod.c │ ├── dc_blocker.c │ ├── fsk_demod.c │ ├── gfsk_mod.c │ ├── clock_recovery_mm.c │ └── interp_fir_filter.c ├── resources │ ├── sdr-modem.service │ └── config.conf ├── tcp_server.h ├── tcp_utils.h ├── queue.h ├── sdr │ ├── file_source.h │ ├── sdr_device.h │ ├── sdr_server_client.h │ ├── plutosdr.h │ ├── sdr_server_api.h │ ├── iio_lib.h │ ├── file_source.c │ └── iio_lib.c ├── sdr_worker.h ├── dsp_worker.h ├── api.h ├── api_utils.h ├── linked_list.h ├── server_config.h ├── main.c ├── tcp_utils.c ├── api_utils.c ├── linked_list.c └── sdr_worker.c ├── test ├── resources │ ├── invalid.format.conf │ ├── invalid.rx_sdr_type.conf │ ├── invalid.timeout.conf │ ├── invalid.tx_sdr_type.conf │ ├── tx.cf32 │ ├── lucky7.cf32 │ ├── nusat.cf32 │ ├── inputnan.cf32 │ ├── pluto_enabled.conf │ ├── processed.s8 │ ├── lucky7.expected.s8 │ ├── lucky7.expected.cf32 │ ├── lucky7.expected.nodc.s8 │ ├── lucky7.expected.47000.cf32 │ ├── lucky7.expected.95000.cf32 │ ├── minimal.conf │ ├── nan.s8 │ ├── test-001.tle │ ├── test-002.tle │ ├── run_tests.sh │ └── full.conf ├── iio_lib_mock.h ├── sdr_server_mock.h ├── utils.h ├── sdr_modem_client.h ├── test_sig_source.c ├── test_gaussian_taps.c ├── test_mmse_fir_interpolator.c ├── test_lpf_taps.c ├── perf_fsk_modem.c ├── test_dc_blocker.c ├── test_server_config.c ├── test_clock_recovery_mm.c ├── test_dsp_worker.c ├── test_quadrature_demod.c ├── test_file_source.c ├── test_fsk_demod.c ├── test_queue.c ├── test_linked_list.c ├── test_sgp4_002.c ├── test_doppler.c ├── test_frequency_modulator.c └── sdr_server_mock.c ├── docs └── design.png ├── .idea ├── sdr-modem.iml ├── codeStyles │ └── codeStyleConfig.xml ├── vcs.xml ├── .gitignore ├── modules.xml └── misc.xml ├── sonar-project.properties ├── .gitignore ├── .project ├── .github └── workflows │ └── cmake.yml ├── api.proto ├── README.md └── CMakeLists.txt /src/sgpsdp/TR/README: -------------------------------------------------------------------------------- 1 | Numerical test result for sgpsdp. 2 | -------------------------------------------------------------------------------- /test/resources/invalid.format.conf: -------------------------------------------------------------------------------- 1 | bind_address=127.0.0.1 -------------------------------------------------------------------------------- /test/resources/invalid.rx_sdr_type.conf: -------------------------------------------------------------------------------- 1 | rx_sdr_type="unknown" -------------------------------------------------------------------------------- /test/resources/invalid.timeout.conf: -------------------------------------------------------------------------------- 1 | read_timeout_seconds=-10 2 | -------------------------------------------------------------------------------- /test/resources/invalid.tx_sdr_type.conf: -------------------------------------------------------------------------------- 1 | tx_sdr_type="unknown" 2 | -------------------------------------------------------------------------------- /docs/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/docs/design.png -------------------------------------------------------------------------------- /test/resources/tx.cf32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/tx.cf32 -------------------------------------------------------------------------------- /src/sgpsdp/TR/Makefile.am: -------------------------------------------------------------------------------- 1 | EXTRA_DIST = \ 2 | README \ 3 | test-001-01.res \ 4 | test-002-01.res 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/resources/lucky7.cf32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/lucky7.cf32 -------------------------------------------------------------------------------- /test/resources/nusat.cf32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/nusat.cf32 -------------------------------------------------------------------------------- /test/resources/inputnan.cf32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/inputnan.cf32 -------------------------------------------------------------------------------- /test/resources/pluto_enabled.conf: -------------------------------------------------------------------------------- 1 | tx_sdr_type="plutosdr" 2 | tx_plutosdr_gain=10.0 3 | tx_plutosdr_timeout_millis=20000 -------------------------------------------------------------------------------- /test/resources/processed.s8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/processed.s8 -------------------------------------------------------------------------------- /.idea/sdr-modem.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/resources/lucky7.expected.s8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/lucky7.expected.s8 -------------------------------------------------------------------------------- /test/resources/lucky7.expected.cf32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/lucky7.expected.cf32 -------------------------------------------------------------------------------- /test/resources/lucky7.expected.nodc.s8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/lucky7.expected.nodc.s8 -------------------------------------------------------------------------------- /test/resources/lucky7.expected.47000.cf32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/lucky7.expected.47000.cf32 -------------------------------------------------------------------------------- /test/resources/lucky7.expected.95000.cf32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-modem/HEAD/test/resources/lucky7.expected.95000.cf32 -------------------------------------------------------------------------------- /src/math/fast_atan2f.h: -------------------------------------------------------------------------------- 1 | #ifndef MATH_FAST_ATAN2F_H_ 2 | #define MATH_FAST_ATAN2F_H_ 3 | 4 | float fast_atan2f(float y, float x); 5 | 6 | #endif /* MATH_FAST_ATAN2F_H_ */ 7 | -------------------------------------------------------------------------------- /test/resources/minimal.conf: -------------------------------------------------------------------------------- 1 | # minimal config has reasonable defaults 2 | # putting random config because libconfig fails with "syntax error" 3 | # if file simply empty 4 | A=1 -------------------------------------------------------------------------------- /test/resources/nan.s8: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/resources/test-001.tle: -------------------------------------------------------------------------------- 1 | TEST SAT SGP 001 2 | 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 9 3 | 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 103 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /test/resources/test-002.tle: -------------------------------------------------------------------------------- 1 | TEST SAT SDP 001 2 | 1 11801U 80230.29629788 .01431103 00000-0 14311-1 0 2 3 | 2 11801 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2 4 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /src/dsp/lpf_taps.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_LPF_H_ 2 | #define SRC_LPF_H_ 3 | 4 | #include 5 | 6 | int create_low_pass_filter(float gain, uint64_t sampling_freq, uint64_t cutoff_freq, uint32_t transition_width, float **taps, size_t *len); 7 | 8 | #endif /* SRC_LPF_H_ */ 9 | -------------------------------------------------------------------------------- /src/dsp/gaussian_taps.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_GAUSSIAN_TAPS_H 2 | #define SDR_MODEM_GAUSSIAN_TAPS_H 3 | 4 | #include 5 | 6 | int gaussian_taps_create(double gain, double samples_per_symbol, double bt, size_t taps_len, float **taps); 7 | 8 | #endif //SDR_MODEM_GAUSSIAN_TAPS_H 9 | -------------------------------------------------------------------------------- /src/sgpsdp/README: -------------------------------------------------------------------------------- 1 | This library is based on the sgp4sdp4-0.3 by Neoklis Kyriazis 2 | (http://leonardo.spidernet.net/Copernicus/22420). I made some minor 3 | modifications to the code in order to create a library that works on 4 | multiple platforms. 5 | 6 | Hari Nair 7 | hari@alumni.caltech.edu 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/resources/sdr-modem.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=sdr-modem Service 3 | 4 | [Service] 5 | WorkingDirectory=/etc/sdr-modem 6 | Environment="VOLK_CONFIGPATH=/etc/sdr-modem" 7 | ExecStart=/usr/bin/sdr_modem /etc/sdr-modem/config.conf 8 | Restart=always 9 | 10 | [Install] 11 | Alias=sdr-modem.service 12 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /test/iio_lib_mock.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_IIO_LIB_MOCK_H 2 | #define SDR_MODEM_IIO_LIB_MOCK_H 3 | 4 | #include "../src/sdr/iio_lib.h" 5 | 6 | int iio_lib_mock_create(int16_t *expected_rx, size_t expected_rx_len, int16_t *expected_tx, iio_lib **lib); 7 | 8 | void iio_lib_mock_get_tx(int16_t **output, size_t *output_len); 9 | 10 | #endif //SDR_MODEM_IIO_LIB_MOCK_H 11 | -------------------------------------------------------------------------------- /src/tcp_server.h: -------------------------------------------------------------------------------- 1 | #ifndef TCP_SERVER_H_ 2 | #define TCP_SERVER_H_ 3 | 4 | #include "server_config.h" 5 | 6 | typedef struct tcp_server_t tcp_server; 7 | 8 | int tcp_server_create(struct server_config *config, tcp_server **server); 9 | 10 | void tcp_server_join_thread(tcp_server *server); 11 | 12 | void tcp_server_destroy(tcp_server *server); 13 | 14 | 15 | #endif /* TCP_SERVER_H_ */ 16 | -------------------------------------------------------------------------------- /src/dsp/dc_blocker.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef DSP_DC_BLOCKER_H_ 3 | #define DSP_DC_BLOCKER_H_ 4 | 5 | typedef struct dc_blocker_t dc_blocker; 6 | 7 | int dc_blocker_create(int length, dc_blocker **blocker); 8 | 9 | void dc_blocker_process(float *input, size_t input_len, float **output, size_t *output_len, dc_blocker *blocker); 10 | 11 | void dc_blocker_destroy(dc_blocker *blocker); 12 | 13 | #endif /* DSP_DC_BLOCKER_H_ */ 14 | -------------------------------------------------------------------------------- /test/resources/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set +e 4 | 5 | EXIT_CODE=0 6 | for file in test_*; do 7 | [[ ${file} == *.dSYM ]] && continue 8 | export VOLK_GENERIC=1 9 | export VOLK_ALIGNMENT=16 10 | valgrind -v --error-exitcode=0 -q --tool=memcheck --leak-check=yes --show-reachable=yes ./${file} 11 | CURRENT_EXIT_CODE=$? 12 | if [ ${CURRENT_EXIT_CODE} != 0 ]; then 13 | EXIT_CODE=${CURRENT_EXIT_CODE} 14 | fi 15 | done 16 | 17 | exit ${EXIT_CODE} 18 | -------------------------------------------------------------------------------- /src/tcp_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_TCP_UTILS_H 2 | #define SDR_MODEM_TCP_UTILS_H 3 | 4 | #include 5 | #include 6 | 7 | int tcp_utils_write_data(uint8_t *buffer, size_t total_len_bytes, int client_socket); 8 | 9 | int tcp_utils_read_data(void *result, size_t len_bytes, int client_socket); 10 | 11 | int tcp_utils_read_data_partially(void *result, size_t len_bytes, size_t *actually_read, int client_socket); 12 | 13 | #endif //SDR_MODEM_TCP_UTILS_H 14 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/dsp/lpf.h: -------------------------------------------------------------------------------- 1 | #ifndef DSP_LPF_H_ 2 | #define DSP_LPF_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct lpf_t lpf; 9 | 10 | int lpf_create(uint8_t decimation, uint64_t sampling_freq, uint64_t cutoff_freq, uint32_t transition_width, size_t output_len, size_t num_bytes, lpf **filter); 11 | 12 | void lpf_process(const void *input, size_t input_len, void **output, size_t *output_len, lpf *filter); 13 | 14 | void lpf_destroy(lpf *filter); 15 | 16 | #endif /* DSP_LPF_H_ */ 17 | -------------------------------------------------------------------------------- /src/dsp/clock_recovery_mm.h: -------------------------------------------------------------------------------- 1 | #ifndef DSP_CLOCK_RECOVERY_MM_H_ 2 | #define DSP_CLOCK_RECOVERY_MM_H_ 3 | 4 | #include 5 | 6 | typedef struct clock_mm_t clock_mm; 7 | 8 | int clock_mm_create(float omega, float gain_omega, float mu, float gain_mu, float omega_relative_limit, size_t output_len, clock_mm **clock); 9 | 10 | void clock_mm_process(const float *input, size_t input_len, float **output, size_t *output_len, clock_mm *clock); 11 | 12 | void clock_mm_destroy(clock_mm *clock); 13 | 14 | #endif /* DSP_CLOCK_RECOVERY_MM_H_ */ 15 | -------------------------------------------------------------------------------- /src/dsp/quadrature_demod.h: -------------------------------------------------------------------------------- 1 | #ifndef DSP_QUADRATURE_DEMOD_H_ 2 | #define DSP_QUADRATURE_DEMOD_H_ 3 | 4 | #include 5 | #include 6 | 7 | typedef struct quadrature_demod_t quadrature_demod; 8 | 9 | int quadrature_demod_create(float gain, uint32_t max_input_buffer_length, quadrature_demod **demod); 10 | 11 | void quadrature_demod_process(float complex *input, size_t input_len, float **output, size_t *output_len, quadrature_demod *demod); 12 | 13 | void quadrature_demod_destroy(quadrature_demod *demod); 14 | 15 | #endif /* DSP_QUADRATURE_DEMOD_H_ */ 16 | -------------------------------------------------------------------------------- /src/dsp/mmse_fir_interpolator.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_MMSE_FIR_INTERPOLATOR_H 2 | #define SDR_MODEM_MMSE_FIR_INTERPOLATOR_H 3 | 4 | #include 5 | 6 | typedef struct mmse_fir_interpolator_t mmse_fir_interpolator; 7 | 8 | int mmse_fir_interpolator_create(mmse_fir_interpolator **interp); 9 | 10 | float mmse_fir_interpolator_process(const float *input, float mu, mmse_fir_interpolator *interp); 11 | 12 | void mmse_fir_interpolator_destroy(mmse_fir_interpolator *interp); 13 | 14 | int mmse_fir_interpolator_taps(mmse_fir_interpolator *interp); 15 | 16 | #endif //SDR_MODEM_MMSE_FIR_INTERPOLATOR_H 17 | -------------------------------------------------------------------------------- /src/queue.h: -------------------------------------------------------------------------------- 1 | #ifndef QUEUE_H_ 2 | #define QUEUE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct queue_t queue; 9 | 10 | int create_queue(uint32_t buffer_size, uint16_t queue_size, bool blocking, queue **queue); 11 | 12 | int queue_put(const float complex *buffer, size_t len, queue *queue); 13 | void take_buffer_for_processing(float complex **buffer, size_t *len, queue *queue); 14 | void complete_buffer_processing(queue *queue); 15 | 16 | void interrupt_waiting_the_data(queue *queue); 17 | void destroy_queue(queue *queue); 18 | 19 | #endif /* QUEUE_H_ */ 20 | -------------------------------------------------------------------------------- /src/dsp/interp_fir_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_INTERP_FIR_FILTER_H 2 | #define SDR_MODEM_INTERP_FIR_FILTER_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct interp_fir_filter_t interp_fir_filter; 8 | 9 | int interp_fir_filter_create(float *taps, size_t taps_len, uint8_t interpolation, uint32_t max_input_buffer_length, interp_fir_filter **filter); 10 | 11 | void interp_fir_filter_process(float *input, size_t input_len, float **output, size_t *output_len, interp_fir_filter *filter); 12 | 13 | void interp_fir_filter_destroy(interp_fir_filter *filter); 14 | 15 | #endif //SDR_MODEM_INTERP_FIR_FILTER_H 16 | -------------------------------------------------------------------------------- /src/dsp/frequency_modulator.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_FREQUENCY_MODULATOR_H 2 | #define SDR_MODEM_FREQUENCY_MODULATOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct frequency_modulator_t frequency_modulator; 9 | 10 | int frequency_modulator_create(float sensitivity, uint32_t max_input_buffer_length, frequency_modulator **mod); 11 | 12 | void frequency_modulator_process(float *input, size_t input_len, float complex **output, size_t *output_len, frequency_modulator *mod); 13 | 14 | void frequency_modulator_destroy(frequency_modulator *mod); 15 | 16 | #endif //SDR_MODEM_FREQUENCY_MODULATOR_H 17 | -------------------------------------------------------------------------------- /src/sdr/file_source.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_FILE_SOURCE_H 2 | #define SDR_MODEM_FILE_SOURCE_H 3 | 4 | #include "sdr_device.h" 5 | 6 | typedef struct file_device_t file_device; 7 | 8 | int file_source_create(uint32_t id, const char *rx_filename, const char *tx_filename, uint64_t sampling_freq, int64_t freq_offset, uint32_t max_output_buffer_length, sdr_device **result); 9 | 10 | int file_source_process_rx(float complex **output, size_t *output_len, void *plugin); 11 | 12 | int file_source_process_tx(float complex *input, size_t input_len, void *plugin); 13 | 14 | void file_source_destroy(void *plugin); 15 | 16 | #endif //SDR_MODEM_FILE_SOURCE_H 17 | -------------------------------------------------------------------------------- /src/dsp/fsk_demod.h: -------------------------------------------------------------------------------- 1 | #ifndef DSP_FSK_DEMOD_H_ 2 | #define DSP_FSK_DEMOD_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct fsk_demod_t fsk_demod; 10 | 11 | int fsk_demod_create(uint64_t sampling_freq, uint32_t baud_rate, int64_t deviation, uint8_t decimation, uint32_t transition_width, bool use_dc_block, uint32_t max_input_buffer_length, fsk_demod **demod); 12 | 13 | void fsk_demod_process(const float complex *input, size_t input_len, int8_t **output, size_t *output_len, fsk_demod *demod); 14 | 15 | void fsk_demod_destroy(fsk_demod *demod); 16 | 17 | #endif /* DSP_FSK_DEMOD_H_ */ 18 | -------------------------------------------------------------------------------- /src/sdr_worker.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_SDR_WORKER_H 2 | #define SDR_MODEM_SDR_WORKER_H 3 | 4 | #include 5 | #include 6 | #include "dsp_worker.h" 7 | #include "sdr/sdr_device.h" 8 | 9 | typedef struct sdr_worker_t sdr_worker; 10 | 11 | void sdr_worker_destroy(void *data); 12 | 13 | bool sdr_worker_find_closest(void *id, void *data); 14 | 15 | void sdr_worker_destroy_by_dsp_worker_id(uint32_t id, sdr_worker *sdr); 16 | 17 | int sdr_worker_add_dsp_worker(dsp_worker *worker, sdr_worker *sdr); 18 | 19 | int sdr_worker_create(uint32_t id, struct sdr_rx *rx, sdr_device *rx_device, sdr_worker **result); 20 | 21 | #endif //SDR_MODEM_SDR_WORKER_H 22 | -------------------------------------------------------------------------------- /src/dsp/gfsk_mod.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_GFSK_MOD_H 2 | #define SDR_MODEM_GFSK_MOD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct gfsk_mod_t gfsk_mod; 9 | 10 | int gfsk_mod_create(float samplesPerSymbol, float sensitivity, float bt, uint32_t max_input_buffer_length, gfsk_mod **mod); 11 | 12 | void gfsk_mod_process(const uint8_t *input, size_t input_len, float complex **output, size_t *output_len, gfsk_mod *mod); 13 | 14 | //used in tests 15 | int gfsk_mod_convolve(float *x, size_t x_len, float *y, size_t y_len, float **out, size_t *out_len); 16 | 17 | void gfsk_mod_destroy(gfsk_mod *mod); 18 | 19 | #endif //SDR_MODEM_GFSK_MOD_H 20 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=dernasherbrezon_sdr-modem 2 | sonar.organization=dernasherbrezon-github 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | sonar.projectName=sdr-modem 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | sonar.sources=./src/ 10 | sonar.tests=./test/ 11 | 12 | # Encoding of the source code. Default is default system encoding 13 | sonar.sourceEncoding=UTF-8 14 | 15 | sonar.cfamily.gcov.reportsPath=build 16 | 17 | sonar.cpp.file.suffixes=- 18 | sonar.objc.file.suffixes=- 19 | 20 | #sonar.cfamily.build-wrapper-output=debug/bw-output 21 | sonar.cfamily.threads=2 -------------------------------------------------------------------------------- /src/sdr/sdr_device.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_SDR_DEVICE_H 2 | #define SDR_MODEM_SDR_DEVICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct sdr_device_t sdr_device; 9 | 10 | struct sdr_rx { 11 | uint64_t rx_center_freq; 12 | uint64_t rx_sampling_freq; 13 | int64_t rx_offset; 14 | }; 15 | 16 | struct sdr_device_t { 17 | void *plugin; 18 | 19 | int (*sdr_process_rx)(float complex **output, size_t *output_len, void *plugin); 20 | int (*sdr_process_tx)(float complex *input, size_t input_len, void *plugin); 21 | void (*destroy)(void *plugin); 22 | void (*stop_rx)(void *plugin); 23 | }; 24 | 25 | #endif //SDR_MODEM_SDR_DEVICE_H 26 | -------------------------------------------------------------------------------- /test/sdr_server_mock.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_SDR_SERVER_MOCK_H 2 | #define SDR_MODEM_SDR_SERVER_MOCK_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct sdr_server_mock_t sdr_server_mock; 9 | 10 | int sdr_server_mock_create(const char *addr, int port, void (*handler)(int client_socket, sdr_server_mock *server), uint32_t max_output_buffer_length, sdr_server_mock **server); 11 | 12 | void mock_response_success(int client_socket, sdr_server_mock *server); 13 | 14 | int sdr_server_mock_send(float complex *input, size_t input_len, sdr_server_mock *server); 15 | 16 | void sdr_server_mock_destroy(sdr_server_mock *server); 17 | 18 | #endif //SDR_MODEM_SDR_SERVER_MOCK_H 19 | -------------------------------------------------------------------------------- /src/dsp/sig_source.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_SIG_SOURCE_H 2 | #define SDR_MODEM_SIG_SOURCE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct sig_source_t sig_source; 9 | 10 | int sig_source_create(float amplitude, uint64_t rx_sampling_freq, uint32_t max_output_buffer_length, sig_source **source); 11 | 12 | void sig_source_process(int64_t freq, size_t expected_output_len, float complex **output, size_t *output_len, sig_source *source); 13 | 14 | void sig_source_multiply(int64_t freq, const float complex *input, size_t input_len, float complex **output, size_t *output_len, sig_source *source); 15 | 16 | void sig_source_destroy(sig_source *source); 17 | 18 | #endif //SDR_MODEM_SIG_SOURCE_H 19 | -------------------------------------------------------------------------------- /src/dsp_worker.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_DSP_WORKER_H 2 | #define SDR_MODEM_DSP_WORKER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "api.pb-c.h" 10 | #include "server_config.h" 11 | 12 | typedef struct dsp_worker_t dsp_worker; 13 | 14 | void dsp_worker_destroy(void *data); 15 | 16 | bool dsp_worker_find_by_id(void *id, void *data); 17 | 18 | void dsp_worker_shutdown(void *arg, void *data); 19 | 20 | void dsp_worker_put(float complex *output, size_t output_len, dsp_worker *worker); 21 | 22 | int dsp_worker_create(uint32_t id, int client_socket, struct server_config *config, struct RxRequest *req, dsp_worker **result); 23 | 24 | #endif //SDR_MODEM_DSP_WORKER_H 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.fsk_mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | /.settings/ 54 | /build/ 55 | /Debug/ 56 | /cmake-build-debug/ 57 | -------------------------------------------------------------------------------- /src/api.h: -------------------------------------------------------------------------------- 1 | #ifndef API_H_ 2 | #define API_H_ 3 | 4 | #include 5 | 6 | #define PROTOCOL_VERSION 0 7 | 8 | // client to server 9 | #define TYPE_RX_REQUEST 0 10 | #define TYPE_SHUTDOWN 1 11 | #define TYPE_PING 3 12 | #define TYPE_TX_DATA 4 13 | #define TYPE_TX_REQUEST 5 14 | //server to client 15 | #define TYPE_RESPONSE 2 16 | 17 | #define RESPONSE_NO_DETAILS 0 18 | #define RESPONSE_DETAILS_INVALID_REQUEST 1 19 | #define RESPONSE_DETAILS_INTERNAL_ERROR 3 20 | #define RESPONSE_DETAILS_TX_IS_BEING_USED 4 21 | #define RESPONSE_DETAILS_RX_IS_BEING_USED 5 22 | 23 | struct message_header { 24 | uint8_t protocol_version; 25 | uint8_t type; 26 | uint32_t message_length; 27 | } __attribute__((packed)); 28 | 29 | #endif /* API_H_ */ 30 | -------------------------------------------------------------------------------- /src/api_utils.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SDR_MODEM_API_UTILS_H 3 | #define SDR_MODEM_API_UTILS_H 4 | 5 | #include "api.h" 6 | #include "api.pb-c.h" 7 | 8 | int api_utils_read_header(int socket, struct message_header *header); 9 | 10 | int api_utils_read_rx_request(int socket, const struct message_header *header, struct RxRequest **request); 11 | 12 | int api_utils_read_tx_request(int socket, const struct message_header *header, struct TxRequest **request); 13 | 14 | int api_utils_read_tx_data(int socket, const struct message_header *header, struct TxData **request); 15 | 16 | int api_utils_write_response(int socket, ResponseStatus status, uint32_t details); 17 | 18 | void api_utils_convert_tle(char **tle, char (*output)[80]); 19 | 20 | #endif //SDR_MODEM_API_UTILS_H 21 | -------------------------------------------------------------------------------- /src/sdr/sdr_server_client.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_CLIENT_H_ 2 | #define SDR_SERVER_CLIENT_H_ 3 | 4 | #include 5 | #include 6 | #include "sdr_server_api.h" 7 | #include "sdr_device.h" 8 | 9 | typedef struct sdr_server_client_t sdr_server_client; 10 | 11 | int sdr_server_client_create(uint32_t id, struct sdr_rx *rx, char *addr, int port, int read_timeout_seconds, uint32_t max_output_buffer_length, sdr_device **result); 12 | 13 | int sdr_server_client_read_stream(float complex **output, size_t *output_len, void *plugin); 14 | 15 | int sdr_server_client_request(struct sdr_server_request request, struct sdr_server_response **response, sdr_server_client *client); 16 | 17 | void sdr_server_client_stop(void *plugin); 18 | 19 | void sdr_server_client_destroy(void *plugin); 20 | 21 | #endif /* SDR_SERVER_CLIENT_H_ */ 22 | -------------------------------------------------------------------------------- /src/dsp/doppler.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_DOPPLER_H 2 | #define SDR_MODEM_DOPPLER_H 3 | 4 | #define _POSIX_C_SOURCE 200809L 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | typedef struct doppler_t doppler; 13 | 14 | int doppler_create(double latitude, double longitude, double altitude, uint64_t sampling_freq, uint64_t center_freq, int64_t constant_offset, time_t start_time_seconds, uint32_t max_output_buffer_length, char tle[3][80], doppler **result); 15 | 16 | void doppler_process_rx(float complex *input, size_t input_len, float complex **output, size_t *output_len, doppler *result); 17 | 18 | void doppler_process_tx(float complex *input, size_t input_len, float complex **output, size_t *output_len, doppler *result); 19 | 20 | void doppler_destroy(doppler *result); 21 | 22 | #endif //SDR_MODEM_DOPPLER_H 23 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | sdr-modem 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.cdt.managedbuilder.core.genmakebuilder 10 | clean,full,incremental, 11 | 12 | 13 | 14 | 15 | org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder 16 | full,incremental, 17 | 18 | 19 | 20 | 21 | 22 | org.eclipse.cdt.core.cnature 23 | org.eclipse.cdt.managedbuilder.core.managedBuildNature 24 | org.eclipse.cdt.managedbuilder.core.ScannerConfigNature 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/linked_list.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_LINKED_LIST_H 2 | #define SDR_MODEM_LINKED_LIST_H 3 | 4 | #include 5 | 6 | typedef struct linked_list_t linked_list; 7 | 8 | int linked_list_add(void *data, void (*destructor)(void *data), linked_list **list); 9 | 10 | void linked_list_destroy_by_selector(bool (*selector)(void *data), linked_list **list); 11 | 12 | void linked_list_foreach(void *arg, void (*foreach)(void *arg, void *data), linked_list *list); 13 | 14 | void linked_list_destroy_by_id(void *id, bool (*selector)(void *id, void *data), linked_list **list); 15 | 16 | void *linked_list_remove_by_id(void *id, bool (*selector)(void *id, void *data), linked_list **list); 17 | 18 | void *linked_list_find(void *id, bool (*selector)(void *id, void *data), linked_list *list); 19 | 20 | void linked_list_destroy(linked_list *list); 21 | 22 | #endif //SDR_MODEM_LINKED_LIST_H 23 | -------------------------------------------------------------------------------- /src/dsp/gaussian_taps.c: -------------------------------------------------------------------------------- 1 | #include "gaussian_taps.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #ifndef M_PI 7 | #define M_PI 3.14159265358979323846 8 | #endif 9 | 10 | int gaussian_taps_create(double gain, double samples_per_symbol, double bt, size_t taps_len, float **taps) { 11 | float *result = malloc(sizeof(float) * taps_len); 12 | if (result == NULL) { 13 | return -ENOMEM; 14 | } 15 | 16 | double scale = 0; 17 | double dt = 1.0 / samples_per_symbol; 18 | double s = 1.0 / (sqrt(log(2.0)) / (2 * M_PI * bt)); 19 | double t0 = -0.5 * taps_len; 20 | double ts; 21 | for (int i = 0; i < taps_len; i++) { 22 | t0++; 23 | ts = s * dt * t0; 24 | result[i] = exp(-0.5 * ts * ts); 25 | scale += result[i]; 26 | } 27 | for (int i = 0; i < taps_len; i++) { 28 | result[i] = result[i] / scale * gain; 29 | } 30 | 31 | *taps = result; 32 | return 0; 33 | } -------------------------------------------------------------------------------- /src/dsp/fir_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_FIR_FILTER_H 2 | #define SDR_MODEM_FIR_FILTER_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct fir_filter_t fir_filter; 8 | 9 | struct fir_filter_t { 10 | uint8_t decimation; 11 | 12 | float **taps; 13 | size_t aligned_taps_len; 14 | size_t alignment; 15 | size_t taps_len; 16 | float *original_taps; 17 | 18 | void *working_buffer; 19 | size_t history_offset; 20 | size_t working_len_total; 21 | void *volk_output; 22 | size_t max_input_buffer_length; 23 | 24 | void *output; 25 | size_t output_len; 26 | size_t num_bytes; 27 | }; 28 | 29 | int fir_filter_create(uint8_t decimation, float *taps, size_t taps_len, size_t output_len, size_t num_bytes, fir_filter **filter); 30 | 31 | void fir_filter_process(const void *input, size_t input_len, void **output, size_t *output_len, fir_filter *filter); 32 | 33 | float fir_filter_process_float_single(const float *input, fir_filter *filter); 34 | 35 | void fir_filter_destroy(fir_filter *filter); 36 | 37 | 38 | #endif //SDR_MODEM_FIR_FILTER_H 39 | -------------------------------------------------------------------------------- /src/sgpsdp/1_COPYING: -------------------------------------------------------------------------------- 1 | Copyright Information: 2 | 3 | Some parts of the C source code in this package were ported partly 4 | form NORAD's Spacetrack report #3, which included FORTRAN source for 5 | SGP, SGP4, SDP4, SGP8 and SDP8. According to a statement in that 6 | report, the document is free of copyrights and open to unlimited 7 | public distribution. 8 | 9 | Other parts of the C source code in this package were ported from 10 | Pascal code provided by Dr. TS. Kelso on his CelesTrack website at 11 | http://celestrack.com/ . I have reproduced the copyright information 12 | regarding the Pascal source I used below: 13 | 14 | { Author: Dr TS Kelso } 15 | { Original Version: 1991 Oct 30} 16 | { Current Revision: 1992 Sep 03} 17 | { Version: 1.50 } 18 | { Copyright: 1991-1992, All Rights Reserved } 19 | 20 | I make no copyright claims to the C ports in this package as I used 21 | the above material downloaded from the Internet to produce it. 22 | I suppose any sourcecode used from norad.c must contain the above 23 | copyright statement by Dr. TS. Kelso. 24 | 25 | Neoklis Kyriazis April 10 2001. -------------------------------------------------------------------------------- /src/sdr/plutosdr.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_PLUTOSDR_H 2 | #define SDR_MODEM_PLUTOSDR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "sdr_device.h" 8 | #include "iio_lib.h" 9 | 10 | typedef struct plutosdr_t plutosdr; 11 | 12 | #define IIO_GAIN_MODE_MANUAL 0 13 | #define IIO_GAIN_MODE_FAST_ATTACK 1 14 | #define IIO_GAIN_MODE_SLOW_ATTACK 2 15 | #define IIO_GAIN_MODE_HYBRID 3 16 | 17 | struct stream_cfg { 18 | uint64_t sampling_freq; // Baseband sample rate in Hz 19 | uint64_t center_freq; // Local oscillator frequency in Hz 20 | uint8_t gain_control_mode; 21 | int64_t offset; 22 | double manual_gain; 23 | }; 24 | 25 | int plutosdr_create(uint32_t id, bool rx_only, struct stream_cfg *rx_config, struct stream_cfg *tx_config, unsigned int timeout_ms, uint32_t max_input_buffer_length, iio_lib *lib, sdr_device **result); 26 | 27 | int plutosdr_process_rx(float complex **output, size_t *output_len, void *plugin); 28 | 29 | int plutosdr_process_tx(float complex *input, size_t input_len, void *plugin); 30 | 31 | void plutosdr_destroy(void *plugin); 32 | 33 | #endif //SDR_MODEM_PLUTOSDR_H 34 | -------------------------------------------------------------------------------- /src/server_config.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_CONFIG_H_ 2 | #define SERVER_CONFIG_H_ 3 | 4 | #include 5 | #include 6 | #include "sdr/iio_lib.h" 7 | 8 | #define RX_SDR_TYPE_SDR_SERVER 0 9 | #define RX_SDR_TYPE_PLUTOSDR 1 10 | #define RX_SDR_TYPE_FILE 2 11 | 12 | #define TX_SDR_TYPE_NONE 0 13 | #define TX_SDR_TYPE_PLUTOSDR 1 14 | #define TX_SDR_TYPE_FILE 2 15 | 16 | struct server_config { 17 | // socket settings 18 | char* bind_address; 19 | uint16_t port; 20 | int read_timeout_seconds; 21 | 22 | uint32_t buffer_size; 23 | uint16_t queue_size; 24 | 25 | uint8_t rx_sdr_type; 26 | char *rx_sdr_server_address; 27 | int rx_sdr_server_port; 28 | 29 | // output settings 30 | char *base_path; 31 | 32 | char *rx_file_base_path; 33 | char *tx_file_base_path; 34 | 35 | uint8_t tx_sdr_type; 36 | double tx_plutosdr_gain; 37 | double rx_plutosdr_gain; 38 | unsigned int tx_plutosdr_timeout_millis; 39 | iio_lib *iio; 40 | }; 41 | 42 | int server_config_create(struct server_config **config, const char *path); 43 | 44 | void server_config_destroy(struct server_config *config); 45 | 46 | #endif /* SERVER_CONFIG_H_ */ 47 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "server_config.h" 6 | #include "tcp_server.h" 7 | 8 | static tcp_server *server = NULL; 9 | 10 | void sdrmodem_stop_async(int signum) { 11 | tcp_server_destroy(server); 12 | server = NULL; 13 | } 14 | 15 | int main(int argc, char **argv) { 16 | if (argc < 2) { 17 | fprintf(stderr, "<3>parameter missing: configuration file\n"); 18 | exit(EXIT_FAILURE); 19 | } 20 | setvbuf(stdout, NULL, _IOLBF, 0); 21 | 22 | struct server_config *server_config = NULL; 23 | int code = server_config_create(&server_config, argv[1]); 24 | if (code != 0) { 25 | exit(EXIT_FAILURE); 26 | } 27 | 28 | signal(SIGINT, sdrmodem_stop_async); 29 | signal(SIGHUP, sdrmodem_stop_async); 30 | signal(SIGTERM, sdrmodem_stop_async); 31 | signal(SIGPIPE, SIG_IGN); 32 | 33 | code = tcp_server_create(server_config, &server); 34 | if (code != 0) { 35 | server_config_destroy(server_config); 36 | exit(EXIT_FAILURE); 37 | } 38 | 39 | // wait here until server terminates 40 | tcp_server_join_thread(server); 41 | 42 | // server will be freed on its own thread 43 | server_config_destroy(server_config); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/sgpsdp/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = TR 2 | 3 | ##noinst_LIBRARIES = libsgp4sdp4.a 4 | 5 | AM_CPPFLAGS = $(all_includes) @PACKAGE_CFLAGS@ 6 | 7 | ##libsgp4sdp4_a_METASOURCES = AUTO 8 | 9 | ##libsgp4sdp4_a_SOURCES = \ 10 | ## solar.c \ 11 | ## sgp_time.c \ 12 | ## sgp_obs.c \ 13 | ## sgp_math.c \ 14 | ## sgp_in.c \ 15 | ## sgp4sdp4.c 16 | 17 | ##libsgp4sdp4_a_LDFLAGS = `pkg-config --libs glib-2.0` 18 | 19 | noinst_PROGRAMS = test-001 test-002 20 | 21 | test_001_SOURCES = \ 22 | solar.c \ 23 | sgp_time.c \ 24 | sgp_obs.c \ 25 | sgp_math.c \ 26 | sgp_in.c \ 27 | sgp4sdp4.c \ 28 | test-001.c 29 | 30 | test_001_LDADD = @PACKAGE_LIBS@ 31 | ##test_001_LDFLAGS = `pkg-config --libs glib-2.0` 32 | 33 | test_002_SOURCES = \ 34 | solar.c \ 35 | sgp_time.c \ 36 | sgp_obs.c \ 37 | sgp_math.c \ 38 | sgp_in.c \ 39 | sgp4sdp4.c \ 40 | test-002.c 41 | 42 | test_002_LDADD = @PACKAGE_LIBS@ 43 | ##test_002_LDFLAGS = `pkg-config --libs glib-2.0` 44 | 45 | EXTRA_DIST = \ 46 | 1_COPYING \ 47 | 2_README \ 48 | README \ 49 | sgp4sdp4.c \ 50 | sgp4sdp4.h \ 51 | sgp_in.c \ 52 | sgp_math.c \ 53 | sgp_obs.c \ 54 | sgp_time.c \ 55 | solar.c \ 56 | test-001.c \ 57 | test-001.tle \ 58 | test-002.c \ 59 | test-002.tle 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/sdr/sdr_server_api.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_API_H_ 2 | #define SDR_SERVER_API_H_ 3 | 4 | #include 5 | 6 | #define SDR_SERVER_PROTOCOL_VERSION 0 7 | 8 | // client to server 9 | #define SDR_SERVER_TYPE_REQUEST 0 10 | #define SDR_SERVER_TYPE_SHUTDOWN 1 11 | #define SDR_SERVER_TYPE_PING 3 12 | //server to client 13 | #define SDR_SERVER_TYPE_RESPONSE 2 14 | 15 | struct sdr_server_message_header { 16 | uint8_t protocol_version; 17 | uint8_t type; 18 | } __attribute__((packed)); 19 | 20 | #define SDR_SERVER_REQUEST_DESTINATION_FILE 0 21 | #define SDR_SERVER_REQUEST_DESTINATION_SOCKET 1 22 | 23 | struct sdr_server_request { 24 | uint32_t center_freq; 25 | uint32_t sampling_rate; 26 | uint32_t band_freq; 27 | uint8_t destination; 28 | } __attribute__((packed)); 29 | 30 | #define SDR_SERVER_RESPONSE_STATUS_SUCCESS 0 31 | #define SDR_SERVER_RESPONSE_STATUS_FAILURE 1 32 | 33 | #define SDR_SERVER_RESPONSE_DETAILS_INVALID_REQUEST 1 34 | #define SDR_SERVER_RESPONSE_DETAILS_OUT_OF_BAND_FREQ 2 35 | #define SDR_SERVER_RESPONSE_DETAILS_INTERNAL_ERROR 3 36 | 37 | struct sdr_server_response { 38 | uint8_t status; 39 | uint32_t details; // on success contains file index, on error contains error code 40 | } __attribute__((packed)); 41 | 42 | 43 | #endif /* SDR_SERVER_API_H_ */ 44 | -------------------------------------------------------------------------------- /test/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_UTILS_H 2 | #define SDR_MODEM_UTILS_H 3 | 4 | #include 5 | #include 6 | #include "../src/api.pb-c.h" 7 | #include 8 | 9 | struct RxRequest *create_rx_request(); 10 | 11 | struct TxRequest *create_tx_request(); 12 | 13 | void setup_input_data(float **input, size_t input_offset, size_t len); 14 | 15 | void setup_volk_input_data(float **input, size_t input_offset, size_t len); 16 | 17 | void setup_input_complex_data(float complex **input, size_t input_offset, size_t len); 18 | 19 | void assert_float_array(const float expected[], size_t expected_size, float *actual, size_t actual_size); 20 | 21 | void assert_complex_array(const float expected[], size_t expected_size, float complex *actual, size_t actual_size); 22 | 23 | void assert_int16_array(const int16_t expected[], size_t expected_size, int16_t *actual, size_t actual_size); 24 | 25 | void assert_byte_array(const int8_t expected[], size_t expected_size, int8_t *actual, size_t actual_size, int tolerance); 26 | 27 | void assert_files(FILE *expected, size_t expected_total, uint8_t *expected_buffer, uint8_t *actual_buffer, size_t batch, FILE *actual, int tolerance); 28 | 29 | int read_data(uint8_t *output, size_t *output_len, size_t len, FILE *file); 30 | 31 | char *utils_read_and_copy_str(const char *value); 32 | 33 | char ** utils_allocate_tle(char tle[3][80]); 34 | 35 | #endif //SDR_MODEM_UTILS_H 36 | -------------------------------------------------------------------------------- /test/sdr_modem_client.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_SDR_MODEM_CLIENT_H 2 | #define SDR_MODEM_SDR_MODEM_CLIENT_H 3 | 4 | #include 5 | #include "../src/api.h" 6 | #include "../src/api.pb-c.h" 7 | 8 | typedef struct sdr_modem_client_t sdr_modem_client; 9 | 10 | int sdr_modem_client_create(const char *addr, int port, uint32_t max_buffer_length, int read_timeout_seconds, sdr_modem_client **client); 11 | 12 | int sdr_modem_client_write_raw(uint8_t *buffer, size_t buffer_len, sdr_modem_client *client); 13 | 14 | int sdr_modem_client_write_request(struct message_header *header, struct RxRequest *req, sdr_modem_client *client); 15 | 16 | int sdr_modem_client_write_tx_request(struct message_header *header, struct TxRequest *req, sdr_modem_client *client); 17 | 18 | int sdr_modem_client_write_tx(struct message_header *header, struct TxData *req, sdr_modem_client *client); 19 | 20 | int sdr_modem_client_write_tx_raw(struct message_header *header, struct TxData *req, uint32_t req_len, sdr_modem_client *client); 21 | 22 | int sdr_modem_client_read_stream(int8_t **output, size_t expected_read, sdr_modem_client *client); 23 | 24 | int sdr_modem_client_read_response(struct message_header **response_header, struct Response **resp, sdr_modem_client *client); 25 | 26 | void sdr_modem_client_destroy(sdr_modem_client *client); 27 | 28 | // this will wait until server release all resources, stop all threads and closes connection 29 | void sdr_modem_client_destroy_gracefully(sdr_modem_client *client); 30 | 31 | 32 | #endif //SDR_MODEM_SDR_MODEM_CLIENT_H 33 | -------------------------------------------------------------------------------- /test/test_sig_source.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../src/dsp/sig_source.h" 4 | #include "utils.h" 5 | 6 | sig_source *source = NULL; 7 | 8 | START_TEST (test_success) { 9 | int code = sig_source_create(1.0F, 4, 4, &source); 10 | ck_assert_int_eq(code, 0); 11 | 12 | float complex *output = NULL; 13 | size_t output_len = 0; 14 | sig_source_process(1, 4, &output, &output_len, source); 15 | 16 | const float buffer[8] = {1, 0, 0, 1, -1, 0, 0, -1}; 17 | assert_complex_array(buffer, sizeof(buffer) / sizeof(float) / 2, output, output_len); 18 | } 19 | 20 | END_TEST 21 | 22 | void teardown() { 23 | if (source != NULL) { 24 | sig_source_destroy(source); 25 | } 26 | } 27 | 28 | void setup() { 29 | //do nothing 30 | } 31 | 32 | Suite *common_suite(void) { 33 | Suite *s; 34 | TCase *tc_core; 35 | 36 | s = suite_create("sig_source"); 37 | 38 | /* Core test case */ 39 | tc_core = tcase_create("Core"); 40 | 41 | tcase_add_test(tc_core, test_success); 42 | 43 | tcase_add_checked_fixture(tc_core, setup, teardown); 44 | suite_add_tcase(s, tc_core); 45 | 46 | return s; 47 | } 48 | 49 | int main(void) { 50 | int number_failed; 51 | Suite *s; 52 | SRunner *sr; 53 | 54 | s = common_suite(); 55 | sr = srunner_create(s); 56 | 57 | srunner_set_fork_status(sr, CK_NOFORK); 58 | srunner_run_all(sr, CK_NORMAL); 59 | number_failed = srunner_ntests_failed(sr); 60 | srunner_free(sr); 61 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/dsp/lpf.c: -------------------------------------------------------------------------------- 1 | #include "lpf.h" 2 | #include 3 | #include 4 | #include 5 | #include "fir_filter.h" 6 | #include "lpf_taps.h" 7 | 8 | struct lpf_t { 9 | fir_filter *filter; 10 | }; 11 | 12 | int lpf_create(uint8_t decimation, uint64_t sampling_freq, uint64_t cutoff_freq, uint32_t transition_width, 13 | size_t max_input_buffer_length, size_t num_bytes, lpf **filter) { 14 | struct lpf_t *result = malloc(sizeof(struct lpf_t)); 15 | if (result == NULL) { 16 | return -ENOMEM; 17 | } 18 | // init all fields with 0 so that destroy_* method would work 19 | *result = (struct lpf_t) {0}; 20 | 21 | float *taps = NULL; 22 | size_t taps_len = 0; 23 | int code = create_low_pass_filter(1.0F, sampling_freq, cutoff_freq, transition_width, &taps, &taps_len); 24 | if (code != 0) { 25 | lpf_destroy(result); 26 | return code; 27 | } 28 | code = fir_filter_create(decimation, taps, taps_len, max_input_buffer_length, num_bytes, &result->filter); 29 | if (code != 0) { 30 | lpf_destroy(result); 31 | return code; 32 | } 33 | *filter = result; 34 | return 0; 35 | } 36 | 37 | 38 | void lpf_process(const void *input, size_t input_len, void **output, size_t *output_len, lpf *filter) { 39 | fir_filter_process(input, input_len, output, output_len, filter->filter); 40 | } 41 | 42 | void lpf_destroy(lpf *filter) { 43 | if (filter == NULL) { 44 | return; 45 | } 46 | if (filter->filter != NULL) { 47 | fir_filter_destroy(filter->filter); 48 | } 49 | free(filter); 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/tcp_utils.c: -------------------------------------------------------------------------------- 1 | #include "tcp_utils.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int tcp_utils_write_data(uint8_t *buffer, size_t total_len_bytes, int client_socket) { 8 | size_t left = total_len_bytes; 9 | while (left > 0) { 10 | ssize_t written = write(client_socket, buffer + (total_len_bytes - left), left); 11 | if (written < 0) { 12 | return -1; 13 | } 14 | left -= written; 15 | } 16 | return 0; 17 | } 18 | 19 | int tcp_utils_read_data_partially(void *result, size_t len_bytes, size_t *actually_read, int client_socket) { 20 | size_t left = len_bytes; 21 | int code = 0; 22 | while (left > 0) { 23 | ssize_t received = recv(client_socket, (char *) result + (len_bytes - left), left, 0); 24 | if (received < 0) { 25 | if (errno == EWOULDBLOCK || errno == EAGAIN) { 26 | code = -errno; 27 | break; 28 | } 29 | if (errno == EINTR) { 30 | continue; 31 | } 32 | code = -1; 33 | break; 34 | } 35 | // client has closed the socket 36 | if (received == 0) { 37 | code = -1; 38 | break; 39 | } 40 | left -= received; 41 | } 42 | *actually_read = len_bytes - left; 43 | return code; 44 | } 45 | 46 | int tcp_utils_read_data(void *result, size_t len_bytes, int client_socket) { 47 | size_t actually_read = 0; 48 | return tcp_utils_read_data_partially(result, len_bytes, &actually_read, client_socket); 49 | } 50 | -------------------------------------------------------------------------------- /test/test_gaussian_taps.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../src/dsp/gaussian_taps.h" 4 | 5 | float *taps = NULL; 6 | 7 | START_TEST (test_normal) { 8 | int code = gaussian_taps_create(1.5, 2 * (48000.0F / 9600), 0.5, 12, &taps); 9 | ck_assert_int_eq(code, 0); 10 | 11 | const float expected_taps[] = {0.039070457f, 0.07415177f, 0.12205514f, 0.17424175f, 0.21572968f, 0.23164831f, 0.21572968f, 0.17424175f, 0.12205514f, 0.07415177f, 0.039070457f, 0.017854061f}; 12 | 13 | for (int i = 0; i < 12; i++) { 14 | ck_assert_int_eq((int32_t) (expected_taps[i] * 10000), (int32_t) (taps[i] * 10000)); 15 | } 16 | } 17 | 18 | END_TEST 19 | 20 | void teardown() { 21 | if (taps != NULL) { 22 | free(taps); 23 | taps = NULL; 24 | } 25 | } 26 | 27 | void setup() { 28 | //do nothing 29 | } 30 | 31 | Suite *common_suite(void) { 32 | Suite *s; 33 | TCase *tc_core; 34 | 35 | s = suite_create("gaussian_taps"); 36 | 37 | /* Core test case */ 38 | tc_core = tcase_create("Core"); 39 | 40 | tcase_add_test(tc_core, test_normal); 41 | 42 | tcase_add_checked_fixture(tc_core, setup, teardown); 43 | suite_add_tcase(s, tc_core); 44 | 45 | return s; 46 | } 47 | 48 | int main(void) { 49 | int number_failed; 50 | Suite *s; 51 | SRunner *sr; 52 | 53 | s = common_suite(); 54 | sr = srunner_create(s); 55 | 56 | srunner_set_fork_status(sr, CK_NOFORK); 57 | srunner_run_all(sr, CK_NORMAL); 58 | number_failed = srunner_ntests_failed(sr); 59 | srunner_free(sr); 60 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | BASE_VERSION: "1.0" 11 | DEBEMAIL: "gpg@r2cloud.ru" 12 | DEBFULLNAME: "r2cloud" 13 | 14 | jobs: 15 | build: 16 | name: Build and analyze 17 | runs-on: ubuntu-latest 18 | env: 19 | BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 24 | - name: Install sonar-scanner and build-wrapper 25 | uses: SonarSource/sonarcloud-github-c-cpp@v2 26 | - name: install dependencies 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install -y valgrind libconfig-dev check pkg-config libvolk-dev libiio-dev libprotobuf-c-dev 30 | - name: Run build-wrapper 31 | run: | 32 | mkdir build 33 | cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build 34 | build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/ --config Debug 35 | - name: run tests & coverage 36 | run: | 37 | cd build 38 | bash ./run_tests.sh 39 | make coverage 40 | cd .. 41 | - name: Run sonar-scanner 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 45 | run: | 46 | sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" 47 | -------------------------------------------------------------------------------- /test/test_mmse_fir_interpolator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../src/dsp/mmse_fir_interpolator.h" 3 | #include "utils.h" 4 | #include 5 | #include 6 | 7 | mmse_fir_interpolator *interp = NULL; 8 | float *float_input = NULL; 9 | 10 | START_TEST(test_normal) { 11 | int code = mmse_fir_interpolator_create(&interp); 12 | ck_assert_int_eq(code, 0); 13 | setup_volk_input_data(&float_input, 0, 8); 14 | float result = mmse_fir_interpolator_process(float_input, 0.14, interp); 15 | ck_assert(fabsl(3.140217F - result) < 0.001); 16 | } 17 | 18 | END_TEST 19 | 20 | void teardown() { 21 | if (interp != NULL) { 22 | mmse_fir_interpolator_destroy(interp); 23 | interp = NULL; 24 | } 25 | if (float_input != NULL) { 26 | volk_free(float_input); 27 | float_input = NULL; 28 | } 29 | } 30 | 31 | void setup() { 32 | //do nothing 33 | } 34 | 35 | Suite *common_suite(void) { 36 | Suite *s; 37 | TCase *tc_core; 38 | 39 | s = suite_create("mmse_fir_interpolator"); 40 | 41 | /* Core test case */ 42 | tc_core = tcase_create("Core"); 43 | 44 | tcase_add_test(tc_core, test_normal); 45 | 46 | tcase_add_checked_fixture(tc_core, setup, teardown); 47 | suite_add_tcase(s, tc_core); 48 | 49 | return s; 50 | } 51 | 52 | int main(void) { 53 | int number_failed; 54 | Suite *s; 55 | SRunner *sr; 56 | 57 | s = common_suite(); 58 | sr = srunner_create(s); 59 | 60 | srunner_set_fork_status(sr, CK_NOFORK); 61 | srunner_run_all(sr, CK_NORMAL); 62 | number_failed = srunner_ntests_failed(sr); 63 | srunner_free(sr); 64 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 65 | } -------------------------------------------------------------------------------- /src/resources/config.conf: -------------------------------------------------------------------------------- 1 | # bind address for a server 2 | bind_address="127.0.0.1" 3 | 4 | # port for a server 5 | port=8091 6 | 7 | # buffer size for passing the data from SDR 8 | # same buffer setting for passing data between threads 9 | # the bigger buffer the less context switching, but 10 | # bigger latency for RF messages 11 | buffer_size=131072 12 | 13 | # if client requests to save output locally, then 14 | # the base path controls the directory where it is saved 15 | # tmp directory is recommended 16 | base_path="/tmp/" 17 | 18 | # timeout for reading client's requests 19 | # in seconds 20 | # should be positive 21 | read_timeout_seconds=10 22 | 23 | # SDR for receiving the data. Supported values are: 24 | # - "sdr-server" 25 | # - "plutosdr" 26 | # - "file" 27 | rx_sdr_type="sdr-server" 28 | 29 | # SDR for transmitting the data. Supported values are: 30 | # - "none" 31 | # - "plutosdr". This would require libiio installed. 32 | # - "file" 33 | tx_sdr_type="none" 34 | 35 | # plutosdr-specific settings 36 | # tx gain. This passed as-is to "hardwaregain" parameter 37 | # default is 0.0 38 | tx_plutosdr_gain=0.0 39 | # timeout while communicating with the device 40 | # default is 10 seconds (10000) 41 | tx_plutosdr_timeout_millis=10000 42 | # rx gain. This passed as-is to "hardwaregain" parameter 43 | # default is 0.0 44 | rx_plutosdr_gain=0.0 45 | 46 | # sdr server connection details 47 | rx_sdr_server_address="127.0.0.1" 48 | rx_sdr_server_port=8090 49 | 50 | # file connection details 51 | rx_file_base_path="/tmp/" 52 | tx_file_base_path="/tmp/" 53 | 54 | # number of elements in the DSP queue 55 | # the more queue, the better performance spikes handled 56 | # the less queue, the better latency and memory consumption 57 | # total memory = queue_size * buffer_size * number_of_clients 58 | queue_size=64 59 | -------------------------------------------------------------------------------- /test/resources/full.conf: -------------------------------------------------------------------------------- 1 | # bind address for a server 2 | bind_address="127.0.0.1" 3 | 4 | # port for a server 5 | port=8091 6 | 7 | # buffer size for passing the data from SDR 8 | # same buffer setting for passing data between threads 9 | # the bigger buffer the less context switching, but 10 | # bigger latency for RF messages 11 | buffer_size=2048 12 | 13 | # if client requests to save output locally, then 14 | # the base path controls the directory where it is saved 15 | # tmp directory is recommended 16 | base_path="/tmp/" 17 | 18 | # timeout for reading client's requests 19 | # in seconds 20 | # should be positive 21 | read_timeout_seconds=10 22 | 23 | # SDR for receiving the data. Supported values are: 24 | # - "sdr-server" 25 | # - "plutosdr" 26 | # - "file" 27 | rx_sdr_type="sdr-server" 28 | 29 | # SDR for transmitting the data. Supported values are: 30 | # - "none" 31 | # - "plutosdr". This would require libiio installed. 32 | # - "file" 33 | tx_sdr_type="none" 34 | 35 | # plutosdr-specific settings 36 | # tx gain. This passed as-is to "hardwaregain" parameter 37 | # default is 0.0 38 | tx_plutosdr_gain=0.0 39 | # timeout while communicating with the device 40 | # default is 10 seconds (10000) 41 | tx_plutosdr_timeout_millis=10000 42 | # rx gain. This passed as-is to "hardwaregain" parameter 43 | # default is 0.0 44 | rx_plutosdr_gain=0.0 45 | 46 | # sdr server connection details 47 | rx_sdr_server_address="127.0.0.1" 48 | rx_sdr_server_port=8090 49 | 50 | # file connection details 51 | rx_file_base_path="/tmp/" 52 | tx_file_base_path="/tmp/" 53 | 54 | # number of elements in the DSP queue 55 | # the more queue, the better performance spikes handled 56 | # the less queue, the better latency and memory consumption 57 | # total memory = queue_size * buffer_size * number_of_clients 58 | queue_size=64 59 | 60 | 61 | -------------------------------------------------------------------------------- /api.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | enum modem_type { 4 | GMSK = 1; 5 | } 6 | 7 | enum demod_destination { 8 | FILE = 0; 9 | SOCKET = 1; 10 | BOTH = 2; 11 | } 12 | 13 | message doppler_settings { 14 | repeated string tle = 1; 15 | required uint32 latitude = 2; //degrees times 10^6 16 | required uint32 longitude = 3; //degrees times 10^6 17 | required uint32 altitude = 4; //kilometers times 10^6 18 | } 19 | 20 | message fsk_demodulation_settings { 21 | required int64 demod_fsk_deviation = 1; 22 | required uint32 demod_fsk_transition_width = 2; 23 | required bool demod_fsk_use_dc_block = 3; 24 | } 25 | 26 | message fsk_modulation_settings { 27 | required int64 mod_fsk_deviation = 1; 28 | } 29 | 30 | message file_settings { 31 | required string filename = 1; 32 | required uint64 start_time_seconds = 2; 33 | } 34 | 35 | message RxRequest { 36 | required uint64 rx_center_freq = 1; 37 | required uint64 rx_sampling_freq = 2; 38 | required bool rx_dump_file = 3; 39 | required int64 rx_offset = 4; 40 | required modem_type demod_type = 5; 41 | required uint32 demod_baud_rate = 6; 42 | // the actual is uint8 43 | required uint32 demod_decimation = 7; 44 | required demod_destination demod_destination = 8; 45 | 46 | optional doppler_settings doppler = 9; 47 | optional fsk_demodulation_settings fsk_settings = 10; 48 | optional file_settings file_settings = 11; 49 | } 50 | 51 | message TxRequest { 52 | required uint64 tx_center_freq = 1; 53 | required uint64 tx_sampling_freq = 2; 54 | required bool tx_dump_file = 3; 55 | required int64 tx_offset = 4; 56 | required modem_type mod_type = 5; 57 | required uint32 mod_baud_rate = 6; 58 | 59 | optional doppler_settings doppler = 7; 60 | optional fsk_modulation_settings fsk_settings = 8; 61 | optional file_settings file_settings = 9; 62 | } 63 | 64 | enum response_status { 65 | SUCCESS = 0; 66 | FAILURE = 1; 67 | } 68 | 69 | message Response { 70 | required response_status status = 1; 71 | required uint32 details = 2; 72 | } 73 | 74 | message TxData { 75 | required bytes data = 1; 76 | } -------------------------------------------------------------------------------- /src/dsp/frequency_modulator.c: -------------------------------------------------------------------------------- 1 | #include "frequency_modulator.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef M_PI 8 | #define M_PI 3.14159265358979323846 9 | #endif 10 | 11 | #define M_2PI ((float) (2 * M_PI)) 12 | 13 | struct frequency_modulator_t { 14 | float phase; 15 | float sensitivity; 16 | 17 | float complex *output; 18 | size_t output_len; 19 | }; 20 | 21 | int frequency_modulator_create(float sensitivity, uint32_t max_input_buffer_length, frequency_modulator **mod) { 22 | struct frequency_modulator_t *result = malloc(sizeof(struct frequency_modulator_t)); 23 | if (result == NULL) { 24 | return -ENOMEM; 25 | } 26 | // init all fields with 0 so that destroy_* method would work 27 | *result = (struct frequency_modulator_t) {0}; 28 | result->phase = 0; 29 | result->sensitivity = sensitivity; 30 | 31 | result->output_len = max_input_buffer_length; 32 | result->output = malloc(sizeof(float complex) * result->output_len); 33 | if (result->output == NULL) { 34 | frequency_modulator_destroy(result); 35 | return -ENOMEM; 36 | } 37 | *mod = result; 38 | return 0; 39 | } 40 | 41 | void frequency_modulator_process(float *input, size_t input_len, float complex **output, size_t *output_len, frequency_modulator *mod) { 42 | if (input_len > mod->output_len) { 43 | fprintf(stderr, "<3>requested buffer %zu is more than max: %zu\n", input_len, mod->output_len); 44 | *output = NULL; 45 | *output_len = 0; 46 | return; 47 | } 48 | for (size_t i = 0; i < input_len; i++) { 49 | mod->phase = mod->phase + mod->sensitivity * input[i]; 50 | if (mod->phase < -M_2PI) { 51 | mod->phase += M_2PI; 52 | } 53 | if (mod->phase > M_2PI) { 54 | mod->phase -= M_2PI; 55 | } 56 | mod->output[i] = cos(mod->phase) + I * sin(mod->phase); 57 | } 58 | *output = mod->output; 59 | *output_len = input_len; 60 | } 61 | 62 | void frequency_modulator_destroy(frequency_modulator *mod) { 63 | if (mod == NULL) { 64 | return; 65 | } 66 | if (mod->output != NULL) { 67 | free(mod->output); 68 | } 69 | free(mod); 70 | } -------------------------------------------------------------------------------- /src/sgpsdp/solar.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Unit Solar 3 | * Author: Dr TS Kelso 4 | * Original Version: 1990 Jul 29 5 | * Current Revision: 1999 Nov 27 6 | * Version: 1.30 7 | * Copyright: 1990-1999, All Rights Reserved 8 | * 9 | * Ported to C by: Neoklis Kyriazis April 1 2001 10 | */ 11 | 12 | #include "sgp4sdp4.h" 13 | 14 | /* Calculates solar position vector */ 15 | void Calculate_Solar_Position(double _time, vector_t * solar_vector) 16 | { 17 | double mjd, year, T, M, L, e, C, O, Lsa, nu, R, eps; 18 | 19 | mjd = _time - 2415020.0; 20 | year = 1900 + mjd / 365.25; 21 | T = (mjd + Delta_ET(year) / secday) / 36525.0; 22 | M = Radians(Modulus(358.47583 + Modulus(35999.04975 * T, 360.0) 23 | - (0.000150 + 0.0000033 * T) * Sqr(T), 360.0)); 24 | L = Radians(Modulus(279.69668 + Modulus(36000.76892 * T, 360.0) 25 | + 0.0003025 * Sqr(T), 360.0)); 26 | e = 0.01675104 - (0.0000418 + 0.000000126 * T) * T; 27 | C = Radians((1.919460 - (0.004789 + 0.000014 * T) * T) * sin(M) 28 | + (0.020094 - 0.000100 * T) * sin(2 * M) + 29 | 0.000293 * sin(3 * M)); 30 | O = Radians(Modulus(259.18 - 1934.142 * T, 360.0)); 31 | Lsa = Modulus(L + C - Radians(0.00569 - 0.00479 * sin(O)), twopi); 32 | nu = Modulus(M + C, twopi); 33 | R = 1.0000002 * (1 - Sqr(e)) / (1 + e * cos(nu)); 34 | eps = Radians(23.452294 - (0.0130125 + (0.00000164 - 35 | 0.000000503 * T) * T) * T + 36 | 0.00256 * cos(O)); 37 | R = AU * R; 38 | solar_vector->x = R * cos(Lsa); 39 | solar_vector->y = R * sin(Lsa) * cos(eps); 40 | solar_vector->z = R * sin(Lsa) * sin(eps); 41 | solar_vector->w = R; 42 | } 43 | 44 | 45 | /* Calculates stellite's eclipse status and depth */ 46 | int Sat_Eclipsed(vector_t * pos, vector_t * sol, double *depth) 47 | { 48 | double sd_sun, sd_earth, delta; 49 | vector_t Rho, earth; 50 | 51 | /* Determine partial eclipse */ 52 | sd_earth = ArcSin(xkmper / pos->w); 53 | Vec_Sub(sol, pos, &Rho); 54 | sd_sun = ArcSin(__sr__ / Rho.w); 55 | Scalar_Multiply(-1, pos, &earth); 56 | delta = Angle(sol, &earth); 57 | *depth = sd_earth - sd_sun - delta; 58 | if (sd_earth < sd_sun) 59 | return (0); 60 | else if (*depth >= 0) 61 | return (1); 62 | else 63 | return (0); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/sdr/iio_lib.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_MODEM_IIO_LIB_H 2 | #define SDR_MODEM_IIO_LIB_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct iio_lib_t iio_lib; 8 | 9 | struct iio_lib_t { 10 | void *handle; 11 | 12 | void *(*iio_buffer_end)(const struct iio_buffer *buf); 13 | 14 | void *(*iio_buffer_first)(const struct iio_buffer *buf, const struct iio_channel *chn); 15 | 16 | ssize_t (*iio_buffer_push_partial)(struct iio_buffer *buf, size_t samples_count); 17 | 18 | ssize_t (*iio_buffer_refill)(struct iio_buffer *buf); 19 | 20 | struct iio_device *(*iio_context_find_device)(const struct iio_context *ctx, const char *name); 21 | 22 | struct iio_channel *(*iio_device_find_channel)(const struct iio_device *dev, const char *name, bool output); 23 | 24 | void (*iio_strerror)(int err, char *dst, size_t len); 25 | 26 | ssize_t (*iio_channel_attr_write)(const struct iio_channel *chn, const char *attr, const char *src); 27 | 28 | int (*iio_channel_attr_write_longlong)(const struct iio_channel *chn, const char *attr, long long val); 29 | 30 | int (*iio_device_attr_write_bool)(const struct iio_device *dev, const char *attr, bool val); 31 | 32 | int (*iio_channel_attr_write_bool)(const struct iio_channel *chn, const char *attr, bool val); 33 | 34 | int (*iio_channel_attr_write_double)(const struct iio_channel *chn, const char *attr, double val); 35 | 36 | ssize_t (*iio_device_attr_write_raw)(const struct iio_device *dev, const char *attr, const void *src, size_t len); 37 | 38 | struct iio_scan_context *(*iio_create_scan_context)(const char *backend, unsigned int flags); 39 | 40 | ssize_t (*iio_scan_context_get_info_list)(struct iio_scan_context *ctx, struct iio_context_info ***info); 41 | 42 | void (*iio_scan_context_destroy)(struct iio_scan_context *ctx); 43 | 44 | const char *(*iio_context_info_get_uri)(const struct iio_context_info *info); 45 | 46 | struct iio_context *(*iio_create_context_from_uri)(const char *uri); 47 | 48 | void (*iio_context_info_list_free)(struct iio_context_info **info); 49 | 50 | int (*iio_context_set_timeout)(struct iio_context *ctx, unsigned int timeout_ms); 51 | 52 | void (*iio_channel_enable)(struct iio_channel *chn); 53 | 54 | struct iio_buffer *(*iio_device_create_buffer)(const struct iio_device *dev, size_t samples_count, bool cyclic); 55 | 56 | void (*iio_buffer_destroy)(struct iio_buffer *buf); 57 | 58 | void (*iio_channel_disable)(struct iio_channel *chn); 59 | 60 | void (*iio_context_destroy)(struct iio_context *ctx); 61 | 62 | }; 63 | 64 | int iio_lib_create(iio_lib **lib); 65 | void iio_lib_destroy(iio_lib *lib); 66 | 67 | #endif //SDR_MODEM_IIO_LIB_H 68 | -------------------------------------------------------------------------------- /test/test_lpf_taps.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../src/dsp/lpf_taps.h" 4 | 5 | float *taps = NULL; 6 | 7 | START_TEST (test_bounds1) { 8 | size_t len; 9 | int code = create_low_pass_filter(1.0F, 0, 1750, 500, &taps, &len); 10 | ck_assert_int_eq(code, -1); 11 | } 12 | END_TEST 13 | 14 | START_TEST (test_bounds2) { 15 | size_t len; 16 | int code = create_low_pass_filter(1.0F, 8000, 5000, 500, &taps, &len); 17 | ck_assert_int_eq(code, -1); 18 | } 19 | END_TEST 20 | 21 | START_TEST (test_bounds3) { 22 | size_t len; 23 | int code = create_low_pass_filter(1.0F, 8000, 1750, 0, &taps, &len); 24 | ck_assert_int_eq(code, -1); 25 | } 26 | END_TEST 27 | 28 | START_TEST (test_lowpassTaps) { 29 | size_t len; 30 | int code = create_low_pass_filter(1.0F, 8000, 1750, 500, &taps, &len); 31 | ck_assert_int_eq(code, 0); 32 | 33 | const float expected_taps[] = { 0.00111410965f, -0.000583702058f, -0.00192639488f, 2.30933896e-18f, 0.00368289859f, 0.00198723329f, -0.0058701504f, -0.00666110823f, 0.0068643163f, 0.0147596458f, -0.00398709066f, -0.0259727165f, -0.0064281947f, 0.0387893915f, 0.0301109217f, -0.0507995859f, -0.0833103433f, 0.0593735874f, 0.310160041f, 0.437394291f, 0.310160041f, 0.0593735874f, -0.0833103433f, -0.0507995859f, 0.0301109217f, 0.0387893915f, -0.0064281947f, -0.0259727165f, -0.00398709066f, 0.0147596458f, 0.0068643163f, -0.00666110823f, -0.0058701504f, 0.00198723329f, 34 | 0.00368289859f, 2.30933896e-18f, -0.00192639488f, -0.000583702058f, 0.00111410965f }; 35 | 36 | ck_assert_uint_eq(len, 39); 37 | for (int i = 0; i < len; i++) { 38 | ck_assert_int_eq((int32_t) (expected_taps[i] * 10000), (int32_t) (taps[i] * 10000)); 39 | } 40 | } 41 | END_TEST 42 | 43 | void teardown() { 44 | if (taps != NULL) { 45 | free(taps); 46 | taps = NULL; 47 | } 48 | } 49 | 50 | void setup() { 51 | //do nothing 52 | } 53 | 54 | Suite* common_suite(void) { 55 | Suite *s; 56 | TCase *tc_core; 57 | 58 | s = suite_create("lpf_taps"); 59 | 60 | /* Core test case */ 61 | tc_core = tcase_create("Core"); 62 | 63 | tcase_add_test(tc_core, test_lowpassTaps); 64 | tcase_add_test(tc_core, test_bounds1); 65 | tcase_add_test(tc_core, test_bounds2); 66 | tcase_add_test(tc_core, test_bounds3); 67 | 68 | tcase_add_checked_fixture(tc_core, setup, teardown); 69 | suite_add_tcase(s, tc_core); 70 | 71 | return s; 72 | } 73 | 74 | int main(void) { 75 | int number_failed; 76 | Suite *s; 77 | SRunner *sr; 78 | 79 | s = common_suite(); 80 | sr = srunner_create(s); 81 | 82 | srunner_set_fork_status(sr, CK_NOFORK); 83 | srunner_run_all(sr, CK_NORMAL); 84 | number_failed = srunner_ntests_failed(sr); 85 | srunner_free(sr); 86 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/sgpsdp/TR/test-001-01.res: -------------------------------------------------------------------------------- 1 | TEST DATA: 2 | TEST SAT SGP 001 3 | 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 9 4 | 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 103 5 | 6 | DEEP_SPACE_EPHEM: 0 (expected 0) 7 | 8 | RESULT EXPECTED DELTA 9 | ------------------------------------------------------------------------------ 10 | STEP 1 t: 0.0 X: 2328.97068761 2328.97048951 0.00019810 (0.00001%) 11 | Y: -5995.22085643 -5995.22076416 0.00009227 (0.00000%) 12 | Z: 1719.97068075 1719.97067261 0.00000814 (0.00000%) 13 | VX: 2.91207226 2.91207230 0.00000004 (0.00000%) 14 | VY: -0.98341533 -0.98341546 0.00000013 (0.00001%) 15 | VZ: -7.09081695 -7.09081703 0.00000008 (0.00000%) 16 | STEP 2 t: 360.0 X: 2456.10753857 2456.10705566 0.00048291 (0.00002%) 17 | Y: -6071.93865906 -6071.93853760 0.00012146 (0.00000%) 18 | Z: 1222.89643564 1222.89727783 0.00084219 (0.00007%) 19 | VX: 2.67938947 2.67938992 0.00000045 (0.00002%) 20 | VY: -0.44828939 -0.44829041 0.00000102 (0.00023%) 21 | VZ: -7.22879242 -7.22879231 0.00000011 (0.00000%) 22 | STEP 3 t: 720.0 X: 2567.56230055 2567.56195068 0.00034987 (0.00001%) 23 | Y: -6112.50386789 -6112.50384522 0.00002267 (0.00000%) 24 | Z: 713.96381249 713.96397400 0.00016151 (0.00002%) 25 | VX: 2.44024579 2.44024599 0.00000020 (0.00001%) 26 | VY: 0.09810893 0.09810869 0.00000024 (0.00025%) 27 | VZ: -7.31995922 -7.31995916 0.00000006 (0.00000%) 28 | STEP 4 t: 1080.0 X: 2663.08919967 2663.09078980 0.00159013 (0.00006%) 29 | Y: -6115.48308263 -6115.48229980 0.00078283 (0.00001%) 30 | Z: 196.40236060 196.39640427 0.00595633 (0.00303%) 31 | VX: 2.19612236 2.19611958 0.00000278 (0.00013%) 32 | VY: 0.65241327 0.65241995 0.00000668 (0.00102%) 33 | VZ: -7.36282406 -7.36282432 0.00000026 (0.00000%) 34 | STEP 5 t: 1440.0 X: 2742.55314743 2742.55133057 0.00181686 (0.00007%) 35 | Y: -6079.67068185 -6079.67144775 0.00076590 (0.00001%) 36 | Z: -326.38672720 -326.38095856 0.00576864 (0.00177%) 37 | VX: 1.94849935 1.94850229 0.00000294 (0.00015%) 38 | VY: 1.21106891 1.21106251 0.00000640 (0.00053%) 39 | VZ: -7.35619329 -7.35619372 0.00000043 (0.00001%) 40 | -------------------------------------------------------------------------------- /src/sgpsdp/TR/test-002-01.res: -------------------------------------------------------------------------------- 1 | TEST DATA: 2 | TEST SAT SDP 001 3 | 1 11801U 80230.29629788 .01431103 00000-0 14311-1 0 2 4 | 2 11801 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2 5 | 6 | DEEP_SPACE_EPHEM: 64 (expected 64) 7 | 8 | RESULT EXPECTED DELTA 9 | --------------------------------------------------------------------------------- 10 | STEP 1 t: 0.0 X: 7473.37235249 7473.37066650 0.00168599 (0.00002%) 11 | Y: 428.95458268 428.95261765 0.00196503 (0.00046%) 12 | Z: 5828.74803892 5828.74786377 0.00017515 (0.00000%) 13 | VX: 5.10715285 5.10715130 0.00000155 (0.00003%) 14 | VY: 6.44468277 6.44468284 0.00000007 (0.00000%) 15 | VZ: -0.18613180 -0.18613096 0.00000084 (0.00045%) 16 | STEP 2 t: 360.0 X: -3305.22249435 -3305.22537232 0.00287797 (0.00009%) 17 | Y: 32410.86724220 32410.86328125 0.00396095 (0.00001%) 18 | Z: -24697.17847749 -24697.17675781 0.00171968 (0.00001%) 19 | VX: -1.30113544 -1.30113538 0.00000006 (0.00000%) 20 | VY: -1.15131484 -1.15131518 0.00000034 (0.00003%) 21 | VZ: -0.28333545 -0.28333528 0.00000017 (0.00006%) 22 | STEP 3 t: 720.0 X: 14271.28902792 14271.28759766 0.00143026 (0.00001%) 23 | Y: 24110.45647174 24110.46411133 0.00763959 (0.00003%) 24 | Z: -4725.76149170 -4725.76837158 0.00687988 (0.00015%) 25 | VX: -0.32050356 -0.32050445 0.00000089 (0.00028%) 26 | VY: 2.67984224 2.67984074 0.00000150 (0.00006%) 27 | VZ: -2.08405317 -2.08405289 0.00000028 (0.00001%) 28 | STEP 4 t: 1080.0 X: -9990.05125819 -9990.05883789 0.00757970 (0.00008%) 29 | Y: 22717.38011629 22717.35522461 0.02489168 (0.00011%) 30 | Z: -23616.90130945 -23616.89066250 0.01064695 (0.00005%) 31 | VX: -1.01667324 -1.01667246 0.00000078 (0.00008%) 32 | VY: -2.29026532 -2.29026759 0.00000227 (0.00010%) 33 | VZ: 0.72892148 0.72892364 0.00000216 (0.00030%) 34 | STEP 5 t: 1440.0 X: 9787.88496660 9787.86975097 0.01521563 (0.00016%) 35 | Y: 33753.34020891 33753.34667969 0.00647078 (0.00002%) 36 | Z: -15030.79330940 -15030.81176758 0.01845818 (0.00012%) 37 | VX: -1.09424947 -1.09425966 0.00001019 (0.00093%) 38 | VY: 0.92359201 0.92358845 0.00000356 (0.00039%) 39 | VZ: -1.52231073 -1.52230928 0.00000145 (0.00010%) 40 | -------------------------------------------------------------------------------- /src/dsp/sig_source.c: -------------------------------------------------------------------------------- 1 | #include "sig_source.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef M_PI 8 | #define M_PI 3.14159265358979323846 9 | #endif 10 | 11 | #define M_2PI ((float) (2 * M_PI)) 12 | 13 | struct sig_source_t { 14 | float phase; 15 | float amplitude; 16 | 17 | float complex *output; 18 | uint32_t output_len; 19 | uint64_t rx_sampling_freq; 20 | }; 21 | 22 | int sig_source_create(float amplitude, uint64_t rx_sampling_freq, uint32_t max_output_buffer_length, sig_source **source) { 23 | struct sig_source_t *result = malloc(sizeof(struct sig_source_t)); 24 | if (result == NULL) { 25 | return -ENOMEM; 26 | } 27 | // init all fields with 0 so that destroy_* method would work 28 | *result = (struct sig_source_t) {0}; 29 | result->phase = 0.0F; 30 | result->amplitude = amplitude; 31 | result->rx_sampling_freq = rx_sampling_freq; 32 | result->output_len = max_output_buffer_length; 33 | result->output = malloc(sizeof(float complex) * result->output_len); 34 | if (result->output == NULL) { 35 | sig_source_destroy(result); 36 | return -ENOMEM; 37 | } 38 | 39 | *source = result; 40 | return 0; 41 | } 42 | 43 | void sig_source_process(int64_t freq, size_t expected_output_len, float complex **output, size_t *output_len, sig_source *source) { 44 | float adjusted_freq = M_2PI * (float) freq / source->rx_sampling_freq; 45 | for (size_t i = 0; i < expected_output_len; i++) { 46 | source->output[i] = cos(source->phase) * source->amplitude + sin(source->phase) * source->amplitude * I; 47 | source->phase += adjusted_freq; 48 | if (source->phase < -M_2PI) { 49 | source->phase += M_2PI; 50 | } 51 | if (source->phase > M_2PI) { 52 | source->phase -= M_2PI; 53 | } 54 | } 55 | 56 | *output = source->output; 57 | *output_len = expected_output_len; 58 | } 59 | 60 | void sig_source_multiply(int64_t freq, const float complex *input, size_t input_len, float complex **output, size_t *output_len, sig_source *source) { 61 | if (input_len > source->output_len) { 62 | fprintf(stderr, "<3>requested buffer %zu is more than max: %u\n", input_len, source->output_len); 63 | *output = NULL; 64 | *output_len = 0; 65 | return; 66 | } 67 | float complex *sig_output = NULL; 68 | size_t sig_output_len = 0; 69 | sig_source_process(freq, input_len, &sig_output, &sig_output_len, source); 70 | 71 | volk_32fc_x2_multiply_32fc(source->output, (const lv_32fc_t *) input, (const lv_32fc_t *) sig_output, (unsigned int) input_len); 72 | 73 | *output = source->output; 74 | *output_len = input_len; 75 | } 76 | 77 | void sig_source_destroy(sig_source *source) { 78 | if (source == NULL) { 79 | return; 80 | } 81 | if (source->output != NULL) { 82 | free(source->output); 83 | } 84 | free(source); 85 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## sdr-modem 2 | 3 | [![CMake](https://github.com/dernasherbrezon/sdr-modem/actions/workflows/cmake.yml/badge.svg?branch=main)](https://github.com/dernasherbrezon/sdr-modem/actions/workflows/cmake.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=dernasherbrezon_sdr-modem&metric=alert_status)](https://sonarcloud.io/dashboard?id=dernasherbrezon_sdr-modem) 4 | 5 | Modem based on software defined radios. 6 | 7 | ## Design 8 | 9 | ![design](docs/design.png?raw=true) 10 | 11 | ## Features 12 | 13 | * TCP-based 14 | * Custom [binary protocol](https://github.com/dernasherbrezon/sdr-modem/blob/main/api.proto) based on protobuf messages. 15 | * Supported modulation/demodulation: 16 | * GMSK 17 | * Supported SDRs: 18 | * [sdr-server](https://github.com/dernasherbrezon/sdr-server) 19 | * [plutosdr](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/adalm-pluto.html) 20 | * file 21 | * Misc: 22 | * Doppler's correction for satellites using SGP4 model 23 | * Save intermittent data onto disk for future analysis/replay 24 | 25 | ## API 26 | 27 | * Defined in the [api.h](https://github.com/dernasherbrezon/sdr-modem/blob/main/src/api.h) 28 | * And in the [api.proto](https://github.com/dernasherbrezon/sdr-modem/blob/main/api.proto) 29 | 30 | 31 | ## Configuration 32 | 33 | Sample configuration with reasonable defaults: 34 | 35 | [https://github.com/dernasherbrezon/sdr-modem/blob/main/src/resources/config.conf](https://github.com/dernasherbrezon/sdr-modem/blob/main/src/resources/config.conf) 36 | 37 | ## Dependencies 38 | 39 | sdr-modem depends on several libraries: 40 | 41 | * [libvolk](https://www.libvolk.org). It is recommended to use the latest version (Currently it is 2.x). After libvolk [installed or built](https://github.com/gnuradio/volk#building-on-most-x86-32-bit-and-64-bit-platforms), it needs to detect optimal kernels. Run the command ```volk_profile``` to generate and save profile. 42 | * [libconfig](https://hyperrealm.github.io/libconfig/libconfig_manual.html) 43 | * [libprotobuf-c](https://github.com/protobuf-c/protobuf-c) 44 | * libz. Should be installed in every operational system 45 | * libm. Same 46 | * [libiio](https://github.com/analogdevicesinc/libiio) for plutosdr SDR (Optional) 47 | * [libcheck](https://libcheck.github.io/check/) for tests (Optional) 48 | 49 | All dependencies can be easily installed from [r2cloud APT repository](https://r2server.ru/apt.html): 50 | 51 | ``` 52 | sudo apt-get install curl lsb-release 53 | curl -fsSL https://leosatdata.com/r2cloud.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/r2cloud.gpg 54 | sudo bash -c "echo \"deb [signed-by=/usr/share/keyrings/r2cloud.gpg] http://apt.leosatdata.com $(lsb_release --codename --short) main\" > /etc/apt/sources.list.d/r2cloud.list" 55 | sudo bash -c "echo \"deb [signed-by=/usr/share/keyrings/r2cloud.gpg] http://apt.leosatdata.com/cpu-generic $(lsb_release --codename --short) main\" > /etc/apt/sources.list.d/r2cloud-generic.list" 56 | sudo apt-get update 57 | sudo apt-get install libvolk2-dev libprotobuf-c-dev libconfig-dev check libiio 58 | ``` 59 | 60 | ## Build 61 | 62 | ``` 63 | mkdir build 64 | cd build 65 | cmake .. 66 | make 67 | ``` 68 | -------------------------------------------------------------------------------- /src/dsp/lpf_taps.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef M_PI 8 | #define M_PI 3.14159265358979323846 9 | #endif 10 | 11 | #include "lpf_taps.h" 12 | #include 13 | 14 | int sanity_check_1f(uint64_t sampling_freq, uint64_t cutoff_freq, uint32_t transition_width) { 15 | if (sampling_freq <= 0) { 16 | fprintf(stderr, "<3>sampling frequency should be positive\n"); 17 | return -1; 18 | } 19 | 20 | if (cutoff_freq <= 0 || (double) cutoff_freq > (double) sampling_freq / 2) { 21 | fprintf(stderr, "<3>cutoff frequency should be positive and less than sampling freq / 2. got: %" PRIu64 "\n", cutoff_freq); 22 | return -1; 23 | } 24 | 25 | if (transition_width <= 0) { 26 | fprintf(stderr, "<3>transition width should be positive\n"); 27 | return -1; 28 | } 29 | 30 | return 0; 31 | } 32 | 33 | int computeNtaps(uint64_t sampling_freq, uint64_t transition_width) { 34 | double a = 53; 35 | int ntaps = (int) (a * (double) sampling_freq / (22.0 * (double) transition_width)); 36 | if ((ntaps & 1) == 0) { // if even... 37 | ntaps++; // ...make odd 38 | } 39 | return ntaps; 40 | } 41 | 42 | int create_hamming_window(int ntaps, float **output) { 43 | float *result = malloc(sizeof(float) * ntaps); 44 | if (result == NULL) { 45 | return -ENOMEM; 46 | } 47 | int m = ntaps - 1; 48 | for (int n = 0; n < ntaps; n++) { 49 | result[n] = (float) (0.54 - 0.46 * cos((2 * M_PI * n) / m)); 50 | } 51 | *output = result; 52 | return 0; 53 | } 54 | 55 | int create_low_pass_filter(float gain, uint64_t sampling_freq, uint64_t cutoff_freq, uint32_t transition_width, float **output_taps, size_t *len) { 56 | int code = sanity_check_1f(sampling_freq, cutoff_freq, transition_width); 57 | if (code != 0) { 58 | return code; 59 | } 60 | 61 | int ntaps = computeNtaps(sampling_freq, transition_width); 62 | float *taps = malloc(sizeof(float) * ntaps); 63 | if (taps == NULL) { 64 | return -ENOMEM; 65 | } 66 | memset(taps, 0, sizeof(float) * ntaps); 67 | 68 | float *w = NULL; 69 | code = create_hamming_window(ntaps, &w); 70 | if (code != 0) { 71 | free(taps); 72 | return code; 73 | } 74 | 75 | int M = (ntaps - 1) / 2; 76 | double fwT0 = 2 * M_PI * (double) cutoff_freq / (double) sampling_freq; 77 | 78 | for (int n = -M; n <= M; n++) { 79 | if (n == 0) { 80 | taps[n + M] = (float) (fwT0 / M_PI * w[n + M]); 81 | } else { 82 | // a little algebra gets this into the more familiar sin(x)/x form 83 | taps[n + M] = (float) (sin((double) n * fwT0) / (n * M_PI) * w[n + M]); 84 | } 85 | } 86 | 87 | free(w); 88 | 89 | float fmax = taps[0 + M]; 90 | for (int n = 1; n <= M; n++) { 91 | fmax += 2 * taps[n + M]; 92 | } 93 | 94 | gain /= fmax; // normalize 95 | 96 | for (int i = 0; i < ntaps; i++) { 97 | taps[i] *= gain; 98 | } 99 | 100 | *output_taps = taps; 101 | *len = ntaps; 102 | return 0; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/dsp/quadrature_demod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "quadrature_demod.h" 7 | #include "../math/fast_atan2f.h" 8 | 9 | struct quadrature_demod_t { 10 | float gain; 11 | 12 | float complex *temp_buffer; 13 | size_t temp_buffer_len; 14 | 15 | float complex *working_buffer; 16 | size_t history_offset; 17 | size_t working_buffer_len; 18 | 19 | float *output; 20 | size_t output_len; 21 | }; 22 | 23 | int quadrature_demod_create(float gain, uint32_t max_input_buffer_length, quadrature_demod **demod) { 24 | struct quadrature_demod_t *result = malloc(sizeof(struct quadrature_demod_t)); 25 | if (result == NULL) { 26 | return -ENOMEM; 27 | } 28 | // init all fields with 0 so that destroy_* method would work 29 | *result = (struct quadrature_demod_t) {0}; 30 | result->gain = gain; 31 | result->output_len = max_input_buffer_length; 32 | result->output = malloc(sizeof(float) * result->output_len); 33 | if (result->output == NULL) { 34 | quadrature_demod_destroy(result); 35 | return -ENOMEM; 36 | } 37 | result->history_offset = 1; 38 | result->working_buffer_len = result->output_len + result->history_offset; 39 | result->working_buffer = malloc(sizeof(float complex) * result->working_buffer_len); 40 | if (result->working_buffer == NULL) { 41 | quadrature_demod_destroy(result); 42 | return -ENOMEM; 43 | } 44 | memset(result->working_buffer, 0, result->working_buffer_len * sizeof(float complex)); 45 | 46 | result->temp_buffer_len = max_input_buffer_length; 47 | result->temp_buffer = malloc(sizeof(float complex) * result->temp_buffer_len); 48 | if (result->temp_buffer == NULL) { 49 | quadrature_demod_destroy(result); 50 | return -ENOMEM; 51 | } 52 | 53 | *demod = result; 54 | return 0; 55 | } 56 | 57 | void quadrature_demod_process(float complex *input, size_t input_len, float **output, size_t *output_len, quadrature_demod *demod) { 58 | if (input_len > demod->output_len) { 59 | fprintf(stderr, "<3>requested buffer %zu is more than max: %zu\n", input_len, demod->output_len); 60 | *output = NULL; 61 | *output_len = 0; 62 | return; 63 | } 64 | memcpy(demod->working_buffer + demod->history_offset, input, sizeof(float complex) * input_len); 65 | volk_32fc_x2_multiply_conjugate_32fc(demod->temp_buffer, &demod->working_buffer[1], &demod->working_buffer[0], input_len); 66 | for (int i = 0; i < input_len; i++) { 67 | demod->output[i] = demod->gain * fast_atan2f(cimagf(demod->temp_buffer[i]), crealf(demod->temp_buffer[i])); 68 | } 69 | memmove(demod->working_buffer, demod->working_buffer + input_len, sizeof(float complex) * demod->history_offset); 70 | 71 | *output = demod->output; 72 | *output_len = input_len; 73 | } 74 | 75 | void quadrature_demod_destroy(quadrature_demod *demod) { 76 | if (demod == NULL) { 77 | return; 78 | } 79 | if (demod->output != NULL) { 80 | free(demod->output); 81 | } 82 | if (demod->working_buffer != NULL) { 83 | free(demod->working_buffer); 84 | } 85 | if (demod->temp_buffer != NULL) { 86 | free(demod->temp_buffer); 87 | } 88 | free(demod); 89 | } 90 | -------------------------------------------------------------------------------- /test/perf_fsk_modem.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include 3 | #include "../src/dsp/fsk_demod.h" 4 | #include "../src/dsp/gfsk_mod.h" 5 | 6 | #ifndef M_PI 7 | #define M_PI 3.14159265358979323846 8 | #endif 9 | 10 | void perf_fsk_demod(); 11 | 12 | void perf_gfsk_mod(); 13 | 14 | int main(void) { 15 | 16 | perf_fsk_demod(); 17 | perf_gfsk_mod(); 18 | 19 | return EXIT_SUCCESS; 20 | } 21 | 22 | void perf_gfsk_mod() { 23 | float sample_rate = 19200; 24 | float baud_rate = 9600; 25 | float deviation = 5000; 26 | float samples_per_symbol = sample_rate / baud_rate; 27 | gfsk_mod *mod = NULL; 28 | int code = gfsk_mod_create(samples_per_symbol, (float) (2 * M_PI * deviation / sample_rate), 0.5F, 2016000, &mod); 29 | if (code != 0) { 30 | return; 31 | } 32 | size_t input_len = 2048; 33 | uint8_t *input = malloc(sizeof(uint8_t) * input_len); 34 | if (input == NULL) { 35 | return; 36 | } 37 | for (size_t i = 0; i < input_len; i++) { 38 | input[i] = (uint8_t) i; 39 | } 40 | 41 | double totalTime = 0.0; 42 | int total = 10; 43 | for (int j = 0; j < total; j++) { 44 | int total_executions = 100; 45 | clock_t begin = clock(); 46 | for (int i = 0; i < total_executions; i++) { 47 | complex float *output = NULL; 48 | size_t output_len = 0; 49 | gfsk_mod_process(input, input_len, &output, &output_len, mod); 50 | } 51 | clock_t end = clock(); 52 | double time_spent = (double) (end - begin) / CLOCKS_PER_SEC; 53 | totalTime += time_spent; 54 | } 55 | // MacBook Air M1 56 | // VOLK_GENERIC=1: 57 | // completed in: 0.054459 seconds 58 | // tuned kernel: 59 | // completed in: 0.044478 seconds 60 | 61 | // Raspberry pi 3 mod B 62 | // VOLK_GENERIC=1: 63 | // completed in: 9.711632 seconds 64 | // tuned kernel: 65 | // completed in: 0.851478 seconds 66 | 67 | printf("gfsk mod completed (average): %f seconds\n", (totalTime / total)); 68 | } 69 | 70 | void perf_fsk_demod() { 71 | fsk_demod *demod = NULL; 72 | int code = fsk_demod_create(48000, 4800, 5000, 2, 2000, true, 2016000, &demod); 73 | if (code != 0) { 74 | return; 75 | } 76 | size_t input_len = 4096; 77 | float complex *input = malloc(sizeof(float complex) * input_len); 78 | if (input == NULL) { 79 | return; 80 | } 81 | for (size_t i = 0; i < input_len; i++) { 82 | input[i] = (uint8_t) i; 83 | } 84 | 85 | double totalTime = 0.0; 86 | int total = 10; 87 | for (int j = 0; j < total; j++) { 88 | int total_executions = 100; 89 | clock_t begin = clock(); 90 | for (int i = 0; i < total_executions; i++) { 91 | int8_t *output = NULL; 92 | size_t output_len = 0; 93 | fsk_demod_process(input, input_len, &output, &output_len, demod); 94 | } 95 | clock_t end = clock(); 96 | double time_spent = (double) (end - begin) / CLOCKS_PER_SEC; 97 | totalTime += time_spent; 98 | } 99 | 100 | // MacBook Air M1 101 | // VOLK_GENERIC=1: 102 | // completed in: 0.037171 seconds 103 | // tuned kernel: 104 | // completed in: 0.036825 seconds 105 | 106 | // Raspberry pi 3 mod B 107 | // VOLK_GENERIC=1: 108 | // completed in: 0.640384 seconds 109 | // tuned kernel: 110 | // completed in: 0.655575 seconds 111 | 112 | printf("fsk demod completed (average): %f seconds\n", (totalTime / total)); 113 | } 114 | -------------------------------------------------------------------------------- /test/test_dc_blocker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../src/dsp/dc_blocker.h" 5 | #include "utils.h" 6 | 7 | dc_blocker *dcblocker = NULL; 8 | float *float_input = NULL; 9 | 10 | START_TEST (test_normal) { 11 | int code = dc_blocker_create(32, &dcblocker); 12 | ck_assert_int_eq(code, 0); 13 | 14 | setup_input_data(&float_input, 0, 200); 15 | float *output = NULL; 16 | size_t output_len = 0; 17 | dc_blocker_process(float_input, 200, &output, &output_len, dcblocker); 18 | 19 | const float expected[] = {0.000000F,-0.000001F,-0.000006F,-0.000020F,-0.000053F,-0.000120F,-0.000240F,-0.000441F,-0.000755F,-0.001227F,-0.001909F,-0.002864F,-0.004166F,-0.005901F,-0.008171F,-0.011089F,-0.014786F,-0.019406F,-0.025114F,-0.032090F,-0.040535F,-0.050669F,-0.062733F,-0.076990F,-0.093727F,-0.113254F,-0.135904F,-0.162040F,-0.192047F,-0.226341F,-0.265366F,-0.309593F,-0.359528F,-0.415700F,-0.478666F,-0.549005F,-0.627312F,-0.714201F,-0.810299F,-0.916243F,-1.032677F,-1.160251F,-1.299616F,-1.451423F,-1.616318F,-1.794941F,-1.987923F,-2.195881F,-2.419418F,-2.659120F,-2.915548F,-3.189244F,-3.480721F,-3.790461F,-4.118916F,-4.466501F,-4.833595F,-5.220534F,-5.627611F,-6.055072F,-6.503113F,-6.971878F,-7.461456F,-6.971878F,-6.503113F,-6.055072F,-5.627611F,-5.220534F,-4.833595F,-4.466501F,-4.118916F,-3.790461F,-3.480721F,-3.189244F,-2.915548F,-2.659120F,-2.419418F,-2.195881F,-1.987923F,-1.794941F,-1.616318F,-1.451424F,-1.299618F,-1.160252F,-1.032677F,-0.916243F,-0.810299F,-0.714201F,-0.627312F,-0.549004F,-0.478664F,-0.415699F,-0.359528F,-0.309593F,-0.265366F,-0.226341F,-0.192047F,-0.162041F,-0.135906F,-0.113255F,-0.093727F,-0.076988F,-0.062729F,-0.050667F,-0.040535F,-0.032089F,-0.025112F,-0.019405F,-0.014786F,-0.011089F,-0.008171F,-0.005901F,-0.004166F,-0.002865F,-0.001911F,-0.001228F,-0.000755F,-0.000443F,-0.000244F,-0.000122F,-0.000053F,-0.000019F,-0.000004F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F,0.000000F}; 20 | assert_float_array(expected, sizeof(expected) / sizeof(float), output, output_len); 21 | } 22 | END_TEST 23 | 24 | void teardown() { 25 | if (dcblocker != NULL) { 26 | dc_blocker_destroy(dcblocker); 27 | dcblocker = NULL; 28 | } 29 | if (float_input != NULL) { 30 | free(float_input); 31 | float_input = NULL; 32 | } 33 | } 34 | 35 | void setup() { 36 | //do nothing 37 | } 38 | 39 | Suite *common_suite(void) { 40 | Suite *s; 41 | TCase *tc_core; 42 | 43 | s = suite_create("quadrature_demod"); 44 | 45 | /* Core test case */ 46 | tc_core = tcase_create("Core"); 47 | 48 | tcase_add_test(tc_core, test_normal); 49 | 50 | tcase_add_checked_fixture(tc_core, setup, teardown); 51 | suite_add_tcase(s, tc_core); 52 | 53 | return s; 54 | } 55 | 56 | int main(void) { 57 | int number_failed; 58 | Suite *s; 59 | SRunner *sr; 60 | 61 | s = common_suite(); 62 | sr = srunner_create(s); 63 | 64 | srunner_set_fork_status(sr, CK_NOFORK); 65 | srunner_run_all(sr, CK_NORMAL); 66 | number_failed = srunner_ntests_failed(sr); 67 | srunner_free(sr); 68 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 69 | } 70 | -------------------------------------------------------------------------------- /test/test_server_config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../src/server_config.h" 5 | 6 | struct server_config *config = NULL; 7 | 8 | START_TEST (test_missing_file) { 9 | int code = server_config_create(&config, "non-existing-configuration-file.conf"); 10 | ck_assert_int_eq(code, -1); 11 | } 12 | 13 | END_TEST 14 | 15 | START_TEST (test_invalid_format) { 16 | int code = server_config_create(&config, "invalid.format.conf"); 17 | ck_assert_int_eq(code, -1); 18 | } 19 | 20 | END_TEST 21 | 22 | START_TEST (test_invalid_timeout) { 23 | int code = server_config_create(&config, "invalid.timeout.conf"); 24 | ck_assert_int_eq(code, -1); 25 | } 26 | 27 | END_TEST 28 | 29 | START_TEST (test_unknown_tx_sdr_type) { 30 | int code = server_config_create(&config, "invalid.tx_sdr_type.conf"); 31 | ck_assert_int_eq(code, -1); 32 | } 33 | END_TEST 34 | 35 | START_TEST (test_unknown_rx_sdr_type) { 36 | int code = server_config_create(&config, "invalid.rx_sdr_type.conf"); 37 | ck_assert_int_eq(code, -1); 38 | } 39 | END_TEST 40 | 41 | START_TEST (test_minimal_config) { 42 | int code = server_config_create(&config, "minimal.conf"); 43 | ck_assert_int_eq(code, 0); 44 | } 45 | 46 | END_TEST 47 | 48 | START_TEST (test_pluto_enabled) { 49 | int code = server_config_create(&config, "pluto_enabled.conf"); 50 | ck_assert_int_eq(code, 0); 51 | ck_assert_int_eq(config->tx_sdr_type, TX_SDR_TYPE_PLUTOSDR); 52 | ck_assert(config->iio != NULL); 53 | ck_assert(fabsl(10.0 - config->tx_plutosdr_gain) < 0.001); 54 | ck_assert_int_eq(config->tx_plutosdr_timeout_millis, 20000); 55 | } 56 | END_TEST 57 | 58 | START_TEST (test_success) { 59 | int code = server_config_create(&config, "full.conf"); 60 | ck_assert_int_eq(code, 0); 61 | ck_assert_str_eq(config->bind_address, "127.0.0.1"); 62 | ck_assert_int_eq(config->port, 8091); 63 | ck_assert_int_eq(config->read_timeout_seconds, 10); 64 | ck_assert_int_eq(config->buffer_size, 2048); 65 | ck_assert_int_eq(config->rx_sdr_type, RX_SDR_TYPE_SDR_SERVER); 66 | ck_assert_str_eq(config->base_path, "/tmp/"); 67 | ck_assert_int_eq(config->queue_size, 64); 68 | ck_assert_int_eq(config->tx_sdr_type, TX_SDR_TYPE_NONE); 69 | ck_assert(config->iio == NULL); 70 | ck_assert(fabsl(0.0 - config->tx_plutosdr_gain) < 0.001); 71 | ck_assert_int_eq(config->tx_plutosdr_timeout_millis, 10000); 72 | } 73 | 74 | END_TEST 75 | 76 | void teardown() { 77 | server_config_destroy(config); 78 | config = NULL; 79 | } 80 | 81 | void setup() { 82 | //do nothing 83 | } 84 | 85 | Suite *common_suite(void) { 86 | Suite *s; 87 | TCase *tc_core; 88 | 89 | s = suite_create("server_config"); 90 | 91 | /* Core test case */ 92 | tc_core = tcase_create("Core"); 93 | 94 | tcase_add_test(tc_core, test_success); 95 | tcase_add_test(tc_core, test_minimal_config); 96 | tcase_add_test(tc_core, test_invalid_timeout); 97 | tcase_add_test(tc_core, test_invalid_format); 98 | tcase_add_test(tc_core, test_missing_file); 99 | tcase_add_test(tc_core, test_unknown_tx_sdr_type); 100 | tcase_add_test(tc_core, test_unknown_rx_sdr_type); 101 | tcase_add_test(tc_core, test_pluto_enabled); 102 | 103 | tcase_add_checked_fixture(tc_core, setup, teardown); 104 | suite_add_tcase(s, tc_core); 105 | 106 | return s; 107 | } 108 | 109 | int main(void) { 110 | int number_failed; 111 | Suite *s; 112 | SRunner *sr; 113 | 114 | s = common_suite(); 115 | sr = srunner_create(s); 116 | 117 | srunner_set_fork_status(sr, CK_NOFORK); 118 | srunner_run_all(sr, CK_NORMAL); 119 | number_failed = srunner_ntests_failed(sr); 120 | srunner_free(sr); 121 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 122 | } 123 | -------------------------------------------------------------------------------- /src/api_utils.c: -------------------------------------------------------------------------------- 1 | #include "api_utils.h" 2 | #include "tcp_utils.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | uint32_t MAX_MESSAGE_LENGTH = 32 * 1024; // kilobyte 9 | 10 | int api_utils_read_header(int socket, struct message_header *header) { 11 | int code = tcp_utils_read_data(header, sizeof(struct message_header), socket); 12 | if (code == 0) { 13 | header->message_length = ntohl(header->message_length); 14 | } 15 | return code; 16 | } 17 | 18 | int api_utils_read_tx_data(int socket, const struct message_header *header, struct TxData **request) { 19 | if (header->message_length > MAX_MESSAGE_LENGTH) { 20 | return -1; 21 | } 22 | uint8_t *buffer = malloc(sizeof(uint8_t) * header->message_length); 23 | if (buffer == NULL) { 24 | return -ENOMEM; 25 | } 26 | int code = tcp_utils_read_data(buffer, header->message_length, socket); 27 | if (code != 0) { 28 | free(buffer); 29 | return -1; 30 | } 31 | TxData *result = tx_data__unpack(NULL, header->message_length, buffer); 32 | free(buffer); 33 | if (result == NULL) { 34 | return -1; 35 | } 36 | *request = result; 37 | return 0; 38 | } 39 | 40 | int api_utils_read_tx_request(int socket, const struct message_header *header, struct TxRequest **request) { 41 | if (header->message_length > MAX_MESSAGE_LENGTH) { 42 | return -1; 43 | } 44 | uint8_t *buffer = malloc(sizeof(uint8_t) * header->message_length); 45 | if (buffer == NULL) { 46 | return -ENOMEM; 47 | } 48 | int code = tcp_utils_read_data(buffer, header->message_length, socket); 49 | if (code != 0) { 50 | return -1; 51 | } 52 | TxRequest *result = tx_request__unpack(NULL, header->message_length, buffer); 53 | free(buffer); 54 | if (result == NULL) { 55 | return -1; 56 | } 57 | *request = result; 58 | return 0; 59 | } 60 | 61 | int api_utils_read_rx_request(int socket, const struct message_header *header, struct RxRequest **request) { 62 | if (header->message_length > MAX_MESSAGE_LENGTH) { 63 | return -1; 64 | } 65 | uint8_t *buffer = malloc(sizeof(uint8_t) * header->message_length); 66 | if (buffer == NULL) { 67 | return -ENOMEM; 68 | } 69 | int code = tcp_utils_read_data(buffer, header->message_length, socket); 70 | if (code != 0) { 71 | return -1; 72 | } 73 | RxRequest *result = rx_request__unpack(NULL, header->message_length, buffer); 74 | free(buffer); 75 | if (result == NULL) { 76 | return -1; 77 | } 78 | *request = result; 79 | return 0; 80 | } 81 | 82 | int api_utils_write_response(int socket, ResponseStatus status, uint32_t details) { 83 | Response response = RESPONSE__INIT; 84 | response.status = status; 85 | response.details = details; 86 | 87 | size_t len = response__get_packed_size(&response); 88 | if (len > UINT32_MAX) { 89 | return -1; 90 | } 91 | 92 | struct message_header header; 93 | header.protocol_version = PROTOCOL_VERSION; 94 | header.type = TYPE_RESPONSE; 95 | header.message_length = htonl((uint32_t) len); 96 | 97 | size_t buffer_len = sizeof(struct message_header) + sizeof(uint8_t) * len; 98 | uint8_t *buffer = malloc(buffer_len); 99 | if (buffer == NULL) { 100 | return -ENOMEM; 101 | } 102 | memcpy(buffer, &header, sizeof(struct message_header)); 103 | response__pack(&response, buffer + sizeof(struct message_header)); 104 | 105 | int code = tcp_utils_write_data(buffer, buffer_len, socket); 106 | free(buffer); 107 | return code; 108 | } 109 | 110 | void api_utils_convert_tle(char **tle, char (*output)[80]) { 111 | for (int i = 0; i < 3; i++) { 112 | strncpy(output[i], tle[i], 80); 113 | } 114 | } -------------------------------------------------------------------------------- /src/linked_list.c: -------------------------------------------------------------------------------- 1 | #include "linked_list.h" 2 | #include 3 | #include 4 | 5 | struct linked_list_t { 6 | struct linked_list_t *next; 7 | 8 | void *data; 9 | 10 | void (*destructor)(void *data); 11 | }; 12 | 13 | int linked_list_add(void *data, void (*destructor)(void *data), linked_list **list) { 14 | struct linked_list_t *result = malloc(sizeof(struct linked_list_t)); 15 | if (result == NULL) { 16 | return -ENOMEM; 17 | } 18 | *result = (struct linked_list_t) {0}; 19 | 20 | result->data = data; 21 | result->destructor = destructor; 22 | 23 | if (*list == NULL) { 24 | *list = result; 25 | } else { 26 | linked_list *node = *list; 27 | while (node->next != NULL) { 28 | node = node->next; 29 | } 30 | node->next = result; 31 | } 32 | return 0; 33 | } 34 | 35 | void linked_list_foreach(void *arg, void (*foreach)(void *arg, void *data), linked_list *list) { 36 | linked_list *cur = list; 37 | while (cur != NULL) { 38 | foreach(arg, cur->data); 39 | cur = cur->next; 40 | } 41 | } 42 | 43 | void *linked_list_find(void *id, bool (*selector)(void *id, void *data), linked_list *list) { 44 | linked_list *cur_node = list; 45 | while (cur_node != NULL) { 46 | if (selector(id, cur_node->data) == true) { 47 | return cur_node->data; 48 | } 49 | cur_node = cur_node->next; 50 | } 51 | return NULL; 52 | } 53 | 54 | void *linked_list_remove_by_id(void *id, bool (*selector)(void *id, void *data), linked_list **list) { 55 | linked_list *cur_node = *list; 56 | linked_list *previous = NULL; 57 | void *result = NULL; 58 | while (cur_node != NULL) { 59 | linked_list *next = cur_node->next; 60 | if (selector(id, cur_node->data) == false) { 61 | previous = cur_node; 62 | cur_node = next; 63 | continue; 64 | } 65 | if (previous == NULL) { 66 | *list = next; 67 | } else { 68 | previous->next = next; 69 | } 70 | result = cur_node->data; 71 | free(cur_node); 72 | break; 73 | } 74 | return result; 75 | } 76 | 77 | void linked_list_destroy_by_id(void *id, bool (*selector)(void *id, void *data), linked_list **list) { 78 | linked_list *cur_node = *list; 79 | linked_list *previous = NULL; 80 | while (cur_node != NULL) { 81 | linked_list *next = cur_node->next; 82 | if (selector(id, cur_node->data) == false) { 83 | previous = cur_node; 84 | cur_node = next; 85 | continue; 86 | } 87 | if (previous == NULL) { 88 | *list = next; 89 | } else { 90 | previous->next = next; 91 | } 92 | cur_node->destructor(cur_node->data); 93 | free(cur_node); 94 | break; 95 | } 96 | } 97 | 98 | void linked_list_destroy_by_selector(bool (*selector)(void *data), linked_list **list) { 99 | linked_list *cur_node = *list; 100 | linked_list *previous = NULL; 101 | while (cur_node != NULL) { 102 | linked_list *next = cur_node->next; 103 | if (selector(cur_node->data) == false) { 104 | previous = cur_node; 105 | cur_node = next; 106 | continue; 107 | } 108 | if (previous == NULL) { 109 | *list = next; 110 | } else { 111 | previous->next = next; 112 | } 113 | cur_node->destructor(cur_node->data); 114 | free(cur_node); 115 | cur_node = next; 116 | } 117 | } 118 | 119 | void linked_list_destroy(linked_list *list) { 120 | if (list == NULL) { 121 | return; 122 | } 123 | linked_list *cur = list; 124 | while (cur != NULL) { 125 | linked_list *next = cur->next; 126 | cur->destructor(cur->data); 127 | free(cur); 128 | cur = next; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/test_clock_recovery_mm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../src/dsp/clock_recovery_mm.h" 6 | #include "utils.h" 7 | 8 | clock_mm *clock = NULL; 9 | float *float_input = NULL; 10 | 11 | START_TEST(test_big_buffers) { 12 | size_t max_input_buffer = 10; 13 | int code = clock_mm_create(2.0F, 0.25F * 0.175F * 0.175F, 0.005f, 0.175f, 0.005f, max_input_buffer, &clock); 14 | ck_assert_int_eq(code, 0); 15 | 16 | setup_input_data(&float_input, 0, max_input_buffer + 1); 17 | float *output = NULL; 18 | size_t output_len = 0; 19 | clock_mm_process(float_input, max_input_buffer + 1, &output, &output_len, clock); 20 | ck_assert_int_eq(output_len, 0); 21 | } 22 | 23 | END_TEST 24 | 25 | START_TEST(test_small_buffers) { 26 | int code = clock_mm_create(2.0F, 0.25F * 0.175F * 0.175F, 0.005f, 0.175f, 0.005f, 100, &clock); 27 | ck_assert_int_eq(code, 0); 28 | 29 | setup_input_data(&float_input, 0, 100); 30 | float *output = NULL; 31 | size_t output_len = 0; 32 | // no input, no output 33 | clock_mm_process(float_input, 0, &output, &output_len, clock); 34 | ck_assert_int_eq(output_len, 0); 35 | clock_mm_process(float_input, 4, &output, &output_len, clock); 36 | ck_assert_int_eq(output_len, 0); 37 | clock_mm_process(float_input + 4, 3, &output, &output_len, clock); 38 | ck_assert_int_eq(output_len, 0); 39 | clock_mm_process(float_input + 7, 1, &output, &output_len, clock); 40 | ck_assert_int_eq(output_len, 1); 41 | ck_assert(fabsl(3.007791F - output[0]) < 0.001); 42 | } 43 | 44 | END_TEST 45 | 46 | START_TEST (test_normal) { 47 | int code = clock_mm_create(2.0F, 0.25F * 0.175F * 0.175F, 0.005f, 0.175f, 0.005f, 100, &clock); 48 | ck_assert_int_eq(code, 0); 49 | 50 | setup_input_data(&float_input, 0, 100); 51 | float *output = NULL; 52 | size_t output_len = 0; 53 | clock_mm_process(float_input, 42, &output, &output_len, clock); 54 | 55 | const float expected[] = {3.007791F, 5.537506F, 7.992135F, 10.434598F, 12.865694F, 15.301093F, 17.738537F, 56 | 20.176548F, 22.611200F, 25.053421F, 27.484411F, 29.919767F, 32.351070F, 34.790939F, 57 | 37.227158F}; 58 | assert_float_array(expected, sizeof(expected) / sizeof(float), output, output_len); 59 | 60 | clock_mm_process(float_input + 42, 36, &output, &output_len, clock); 61 | const float expected2[] = {39.662235F, 42.105213F, 44.534428F, 46.975555F, 49.400551F, 51.844826F, 54.276859F, 62 | 56.714336F, 59.148190F, 61.577019F, 64.029373F, 66.450058F, 68.900490F, 71.325760F, 63 | 73.759659F}; 64 | assert_float_array(expected2, sizeof(expected2) / sizeof(float), output, output_len); 65 | 66 | } 67 | 68 | END_TEST 69 | 70 | void teardown() { 71 | if (clock != NULL) { 72 | clock_mm_destroy(clock); 73 | clock = NULL; 74 | } 75 | if (float_input != NULL) { 76 | free(float_input); 77 | float_input = NULL; 78 | } 79 | } 80 | 81 | void setup() { 82 | //do nothing 83 | } 84 | 85 | Suite *common_suite(void) { 86 | Suite *s; 87 | TCase *tc_core; 88 | 89 | s = suite_create("clock_recovery_mm"); 90 | 91 | /* Core test case */ 92 | tc_core = tcase_create("Core"); 93 | 94 | tcase_add_test(tc_core, test_normal); 95 | tcase_add_test(tc_core, test_small_buffers); 96 | tcase_add_test(tc_core, test_big_buffers); 97 | 98 | tcase_add_checked_fixture(tc_core, setup, teardown); 99 | suite_add_tcase(s, tc_core); 100 | 101 | return s; 102 | } 103 | 104 | int main(void) { 105 | int number_failed; 106 | Suite *s; 107 | SRunner *sr; 108 | 109 | s = common_suite(); 110 | sr = srunner_create(s); 111 | 112 | srunner_set_fork_status(sr, CK_NOFORK); 113 | srunner_run_all(sr, CK_NORMAL); 114 | number_failed = srunner_ntests_failed(sr); 115 | srunner_free(sr); 116 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 117 | } 118 | 119 | 120 | -------------------------------------------------------------------------------- /test/test_dsp_worker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../src/dsp_worker.h" 5 | #include "utils.h" 6 | 7 | dsp_worker *worker = NULL; 8 | struct server_config *config = NULL; 9 | struct RxRequest *req = NULL; 10 | 11 | START_TEST (test_invalid_basepath) { 12 | int code = server_config_create(&config, "full.conf"); 13 | ck_assert_int_eq(code, 0); 14 | free(config->base_path); 15 | config->base_path = utils_read_and_copy_str("/invalidpath/"); 16 | req = create_rx_request(); 17 | req->rx_dump_file = true; 18 | code = dsp_worker_create(1, 0, config, req, &worker); 19 | ck_assert_int_eq(code, -1); 20 | 21 | req->rx_dump_file = false; 22 | req->demod_destination = DEMOD_DESTINATION__FILE; 23 | code = dsp_worker_create(1, 0, config, req, &worker); 24 | ck_assert_int_eq(code, -1); 25 | 26 | } 27 | 28 | END_TEST 29 | 30 | START_TEST (test_invalid_queue_size) { 31 | int code = server_config_create(&config, "full.conf"); 32 | ck_assert_int_eq(code, 0); 33 | config->queue_size = 0; 34 | req = create_rx_request(); 35 | code = dsp_worker_create(1, 0, config, req, &worker); 36 | ck_assert_int_eq(code, -1); 37 | } 38 | 39 | END_TEST 40 | 41 | START_TEST (test_invalid_doppler_configuration) { 42 | int code = server_config_create(&config, "full.conf"); 43 | ck_assert_int_eq(code, 0); 44 | req = create_rx_request(); 45 | for (int i = 0; i < req->doppler->n_tle; i++) { 46 | free(req->doppler->tle[i]); 47 | } 48 | free(req->doppler->tle); 49 | char tle[3][80] = {"0\0", "1 0 0 0 0 00000-0 0 0 0\0", "2 0 0 0 0 0 0 0 0\0"}; 50 | req->doppler->tle = utils_allocate_tle(tle); 51 | code = dsp_worker_create(1, 0, config, req, &worker); 52 | ck_assert_int_eq(code, -1); 53 | } 54 | 55 | END_TEST 56 | 57 | START_TEST (test_invalid_fsk_configuration) { 58 | int code = server_config_create(&config, "full.conf"); 59 | ck_assert_int_eq(code, 0); 60 | req = create_rx_request(); 61 | req->demod_baud_rate = req->rx_sampling_freq; 62 | code = dsp_worker_create(1, 0, config, req, &worker); 63 | ck_assert_int_eq(code, -1); 64 | } 65 | 66 | END_TEST 67 | 68 | START_TEST (test_create_delete) { 69 | int code = server_config_create(&config, "full.conf"); 70 | ck_assert_int_eq(code, 0); 71 | req = create_rx_request(); 72 | uint32_t id = 1; 73 | code = dsp_worker_create(id, 0, config, req, &worker); 74 | ck_assert_int_eq(code, 0); 75 | 76 | bool result = dsp_worker_find_by_id(&id, worker); 77 | ck_assert_int_eq(result, 1); 78 | } 79 | 80 | END_TEST 81 | 82 | void teardown() { 83 | if (worker != NULL) { 84 | dsp_worker_destroy(worker); 85 | worker = NULL; 86 | } 87 | if (config != NULL) { 88 | server_config_destroy(config); 89 | config = NULL; 90 | } 91 | if (req != NULL) { 92 | rx_request__free_unpacked(req, NULL); 93 | req = NULL; 94 | } 95 | } 96 | 97 | void setup() { 98 | //do nothing 99 | } 100 | 101 | Suite *common_suite(void) { 102 | Suite *s; 103 | TCase *tc_core; 104 | 105 | s = suite_create("dsp_worker"); 106 | 107 | /* Core test case */ 108 | tc_core = tcase_create("Core"); 109 | 110 | tcase_add_test(tc_core, test_create_delete); 111 | tcase_add_test(tc_core, test_invalid_fsk_configuration); 112 | tcase_add_test(tc_core, test_invalid_doppler_configuration); 113 | tcase_add_test(tc_core, test_invalid_queue_size); 114 | tcase_add_test(tc_core, test_invalid_basepath); 115 | 116 | tcase_add_checked_fixture(tc_core, setup, teardown); 117 | suite_add_tcase(s, tc_core); 118 | 119 | return s; 120 | } 121 | 122 | int main(void) { 123 | int number_failed; 124 | Suite *s; 125 | SRunner *sr; 126 | 127 | s = common_suite(); 128 | sr = srunner_create(s); 129 | 130 | srunner_set_fork_status(sr, CK_NOFORK); 131 | srunner_run_all(sr, CK_NORMAL); 132 | number_failed = srunner_ntests_failed(sr); 133 | srunner_free(sr); 134 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 135 | } 136 | -------------------------------------------------------------------------------- /test/test_quadrature_demod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../src/dsp/quadrature_demod.h" 5 | #include "utils.h" 6 | 7 | quadrature_demod *quad_demod = NULL; 8 | float complex *complex_input = NULL; 9 | 10 | START_TEST (test_normal) { 11 | int code = quadrature_demod_create(25.4F, 2000, &quad_demod); 12 | ck_assert_int_eq(code, 0); 13 | 14 | setup_input_complex_data(&complex_input, 0, 200); 15 | float *output = NULL; 16 | size_t output_len = 0; 17 | quadrature_demod_process(complex_input, 2, &output, &output_len, quad_demod); 18 | 19 | const float expected[] = {0.000000F,-14.935266F}; 20 | assert_float_array(expected, sizeof(expected) / sizeof(float), output, output_len); 21 | 22 | quadrature_demod_process(complex_input + 2, 198, &output, &output_len, quad_demod); 23 | 24 | const float expected2[] = {-2.203149F,-0.860684F,-0.457606F,-0.283786F,-0.193152F,-0.139943F,-0.106054F,-0.083142F,-0.066930F,-0.055038F,-0.046056F,-0.039107F,-0.033620F,-0.029212F,-0.025618F,-0.022648F,-0.020167F,-0.018072F,-0.016287F,-0.014755F,-0.013428F,-0.012273F,-0.011261F,-0.010369F,-0.009579F,-0.008876F,-0.008248F,-0.007684F,-0.007176F,-0.006717F,-0.006300F,-0.005921F,-0.005576F,-0.005259F,-0.004969F,-0.004702F,-0.004457F,-0.004229F,-0.004019F,-0.003824F,-0.003643F,-0.003475F,-0.003318F,-0.003171F,-0.003034F,-0.002906F,-0.002785F,-0.002672F,-0.002566F,-0.002466F,-0.002371F,-0.002282F,-0.002198F,-0.002119F,-0.002043F,-0.001972F,-0.001904F,-0.001840F,-0.001779F,-0.001721F,-0.001665F,-0.001613F,-0.001563F,-0.001515F,-0.001469F,-0.001425F,-0.001383F,-0.001344F,-0.001305F,-0.001269F,-0.001234F,-0.001200F,-0.001168F,-0.001136F,-0.001107F,-0.001078F,-0.001050F,-0.001024F,-0.000998F,-0.000974F,-0.000950F,-0.000927F,-0.000905F,-0.000884F,-0.000864F,-0.000844F,-0.000825F,-0.000806F,-0.000788F,-0.000771F,-0.000754F,-0.000738F,-0.000723F,-0.000707F,-0.000693F,-0.000678F,-0.000665F,-0.000651F,-0.000638F,-0.000626F,-0.000613F,-0.000601F,-0.000590F,-0.000579F,-0.000568F,-0.000557F,-0.000547F,-0.000537F,-0.000527F,-0.000518F,-0.000508F,-0.000500F,-0.000491F,-0.000482F,-0.000474F,-0.000466F,-0.000458F,-0.000450F,-0.000443F,-0.000436F,-0.000428F,-0.000421F,-0.000415F,-0.000408F,-0.000402F,-0.000395F,-0.000389F,-0.000383F,-0.000377F,-0.000371F,-0.000366F,-0.000360F,-0.000355F,-0.000350F,-0.000345F,-0.000340F,-0.000335F,-0.000330F,-0.000325F,-0.000321F,-0.000316F,-0.000312F,-0.000307F,-0.000303F,-0.000299F,-0.000295F,-0.000291F,-0.000287F,-0.000283F,-0.000279F,-0.000276F,-0.000272F,-0.000269F,-0.000265F,-0.000262F,-0.000258F,-0.000255F,-0.000252F,-0.000249F,-0.000246F,-0.000243F,-0.000240F,-0.000237F,-0.000234F,-0.000231F,-0.000228F,-0.000226F,-0.000223F,-0.000220F,-0.000218F,-0.000215F,-0.000213F,-0.000210F,-0.000208F,-0.000206F,-0.000203F,-0.000201F,-0.000199F,-0.000197F,-0.000194F,-0.000192F,-0.000190F,-0.000188F,-0.000186F,-0.000184F,-0.000182F,-0.000180F,-0.000178F,-0.000176F,-0.000175F,-0.000173F,-0.000171F,-0.000169F,-0.000167F,-0.000166F,-0.000164F,-0.000162F,-0.000161F}; 25 | assert_float_array(expected2, sizeof(expected2) / sizeof(float), output, output_len); 26 | 27 | } 28 | 29 | END_TEST 30 | 31 | void teardown() { 32 | if (quad_demod != NULL) { 33 | quadrature_demod_destroy(quad_demod); 34 | quad_demod = NULL; 35 | } 36 | if (complex_input != NULL) { 37 | free(complex_input); 38 | complex_input = NULL; 39 | } 40 | } 41 | 42 | void setup() { 43 | //do nothing 44 | } 45 | 46 | Suite *common_suite(void) { 47 | Suite *s; 48 | TCase *tc_core; 49 | 50 | s = suite_create("quadrature_demod"); 51 | 52 | /* Core test case */ 53 | tc_core = tcase_create("Core"); 54 | 55 | tcase_add_test(tc_core, test_normal); 56 | 57 | tcase_add_checked_fixture(tc_core, setup, teardown); 58 | suite_add_tcase(s, tc_core); 59 | 60 | return s; 61 | } 62 | 63 | int main(void) { 64 | int number_failed; 65 | Suite *s; 66 | SRunner *sr; 67 | 68 | s = common_suite(); 69 | sr = srunner_create(s); 70 | 71 | srunner_set_fork_status(sr, CK_NOFORK); 72 | srunner_run_all(sr, CK_NORMAL); 73 | number_failed = srunner_ntests_failed(sr); 74 | srunner_free(sr); 75 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 76 | } 77 | -------------------------------------------------------------------------------- /src/dsp/dc_blocker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "dc_blocker.h" 6 | 7 | struct moving_average { 8 | float *delay_line; 9 | int delay_line_len; 10 | int length; 11 | float inDelayed; 12 | float outD1; 13 | }; 14 | 15 | struct dc_blocker_t { 16 | struct moving_average *ma0; 17 | struct moving_average *ma1; 18 | struct moving_average *ma2; 19 | struct moving_average *ma3; 20 | 21 | float *delay_line; 22 | int delay_line_len; 23 | }; 24 | 25 | void moving_average_destroy(struct moving_average *mavg) { 26 | if (mavg == NULL) { 27 | return; 28 | } 29 | if (mavg->delay_line != NULL) { 30 | free(mavg->delay_line); 31 | } 32 | free(mavg); 33 | } 34 | 35 | int moving_average_create(int length, struct moving_average **mavg) { 36 | struct moving_average *result = malloc(sizeof(struct moving_average)); 37 | if (result == NULL) { 38 | return -ENOMEM; 39 | } 40 | // init all fields with 0 so that destroy_* method would work 41 | *result = (struct moving_average) {0}; 42 | result->delay_line_len = length - 1; 43 | result->delay_line = malloc(sizeof(float) * result->delay_line_len); 44 | if (result->delay_line == NULL) { 45 | moving_average_destroy(result); 46 | return -ENOMEM; 47 | } 48 | memset(result->delay_line, 0, sizeof(float) * result->delay_line_len); 49 | result->length = length; 50 | result->inDelayed = 0.0F; 51 | result->outD1 = 0.0F; 52 | *mavg = result; 53 | return 0; 54 | } 55 | 56 | float moving_average_process(float input, struct moving_average *mavg) { 57 | float inDelayed = mavg->inDelayed; 58 | mavg->inDelayed = mavg->delay_line[0]; 59 | memmove(mavg->delay_line, mavg->delay_line + 1, sizeof(float) * (mavg->delay_line_len - 1)); 60 | mavg->delay_line[mavg->delay_line_len - 1] = input; 61 | float y = input - inDelayed + mavg->outD1; 62 | mavg->outD1 = y; 63 | return y / (float) mavg->length; 64 | } 65 | 66 | int dc_blocker_create(int length, dc_blocker **blocker) { 67 | struct dc_blocker_t *result = malloc(sizeof(struct dc_blocker_t)); 68 | if (result == NULL) { 69 | return -ENOMEM; 70 | } 71 | // init all fields with 0 so that destroy_* method would work 72 | *result = (struct dc_blocker_t) {0}; 73 | result->delay_line_len = length - 1; 74 | result->delay_line = malloc(sizeof(float) * result->delay_line_len); 75 | if (result->delay_line == NULL) { 76 | dc_blocker_destroy(result); 77 | return -ENOMEM; 78 | } 79 | memset(result->delay_line, 0, sizeof(float) * result->delay_line_len); 80 | 81 | int code = moving_average_create(length, &result->ma0); 82 | if (code != 0) { 83 | dc_blocker_destroy(result); 84 | return code; 85 | } 86 | code = moving_average_create(length, &result->ma1); 87 | if (code != 0) { 88 | dc_blocker_destroy(result); 89 | return code; 90 | } 91 | code = moving_average_create(length, &result->ma2); 92 | if (code != 0) { 93 | dc_blocker_destroy(result); 94 | return code; 95 | } 96 | code = moving_average_create(length, &result->ma3); 97 | if (code != 0) { 98 | dc_blocker_destroy(result); 99 | return code; 100 | } 101 | *blocker = result; 102 | return 0; 103 | } 104 | 105 | void dc_blocker_process(float *input, size_t input_len, float **output, size_t *output_len, dc_blocker *blocker) { 106 | for (size_t i = 0; i < input_len; i++) { 107 | float y1 = moving_average_process(input[i], blocker->ma0); 108 | float y2 = moving_average_process(y1, blocker->ma1); 109 | float y3 = moving_average_process(y2, blocker->ma2); 110 | float y4 = moving_average_process(y3, blocker->ma3); 111 | 112 | float d = blocker->delay_line[0]; 113 | memmove(blocker->delay_line, blocker->delay_line + 1, sizeof(float) * (blocker->delay_line_len - 1)); 114 | blocker->delay_line[blocker->delay_line_len - 1] = blocker->ma0->inDelayed; 115 | input[i] = d - y4; 116 | } 117 | *output = input; 118 | *output_len = input_len; 119 | } 120 | 121 | void dc_blocker_destroy(dc_blocker *blocker) { 122 | if (blocker == NULL) { 123 | return; 124 | } 125 | if (blocker->delay_line != NULL) { 126 | free(blocker->delay_line); 127 | } 128 | moving_average_destroy(blocker->ma0); 129 | moving_average_destroy(blocker->ma1); 130 | moving_average_destroy(blocker->ma2); 131 | moving_average_destroy(blocker->ma3); 132 | free(blocker); 133 | } 134 | -------------------------------------------------------------------------------- /test/test_file_source.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../src/sdr/file_source.h" 5 | #include "utils.h" 6 | 7 | const char *tmp_folder; 8 | sdr_device *device = NULL; 9 | char filename[4096]; 10 | 11 | START_TEST(test_rx_invalid_arguments) { 12 | int max_output_buffer_length = 2000; 13 | int code = file_source_create(1, "/non-existing-file", NULL, 48000, 1000, max_output_buffer_length, &device); 14 | ck_assert_int_eq(code, -1); 15 | 16 | code = file_source_create(1, filename, NULL, 48000, 1000, max_output_buffer_length, &device); 17 | ck_assert_int_eq(code, 0); 18 | 19 | const float buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 20 | size_t buffer_len = sizeof(buffer) / sizeof(float) / 2; 21 | code = device->sdr_process_tx((complex float *) buffer, buffer_len, device->plugin); 22 | ck_assert_int_eq(code, -1); 23 | } 24 | END_TEST 25 | 26 | START_TEST(test_tx_invalid_arguments) { 27 | int max_output_buffer_length = 2000; 28 | int code = file_source_create(1, NULL, "/", 48000, 1000, max_output_buffer_length, &device); 29 | ck_assert_int_eq(code, -1); 30 | 31 | code = file_source_create(1, NULL, filename, 48000, 1000, max_output_buffer_length, &device); 32 | ck_assert_int_eq(code, 0); 33 | 34 | const float buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 35 | size_t buffer_len = sizeof(buffer) / sizeof(float) / 2; 36 | code = device->sdr_process_tx((complex float *) buffer, max_output_buffer_length + 1, device->plugin); 37 | ck_assert_int_eq(code, -1); 38 | 39 | complex float *output = NULL; 40 | size_t output_len = 0; 41 | code = device->sdr_process_rx(&output, &output_len, device->plugin); 42 | ck_assert_int_eq(code, -1); 43 | } 44 | 45 | END_TEST 46 | 47 | START_TEST (test_rx_offset) { 48 | int code = file_source_create(1, "tx.cf32", NULL, 48000, 1000, 2000, &device); 49 | ck_assert_int_eq(code, 0); 50 | 51 | complex float *output = NULL; 52 | size_t output_len = 0; 53 | code = device->sdr_process_rx(&output, &output_len, device->plugin); 54 | ck_assert_int_eq(code, 0); 55 | 56 | const float expected[10] = {1.000000F, 2.000000F, 2.452230F, 4.357358F, 3.276715F, 7.089650F, 3.405689F, 10.069820F, 2.794229F, 13.160254F}; 57 | size_t expected_len = sizeof(expected) / sizeof(float) / 2; 58 | assert_complex_array(expected, expected_len, output, output_len); 59 | } 60 | 61 | END_TEST 62 | 63 | START_TEST (test_success) { 64 | int code = file_source_create(1, NULL, filename, 48000, 0, 2000, &device); 65 | ck_assert_int_eq(code, 0); 66 | 67 | const float buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 68 | size_t buffer_len = sizeof(buffer) / sizeof(float) / 2; 69 | code = device->sdr_process_tx((complex float *) buffer, buffer_len, device->plugin); 70 | ck_assert_int_eq(code, 0); 71 | device->destroy(device->plugin); 72 | free(device); 73 | device = NULL; 74 | 75 | code = file_source_create(1, filename, NULL, 48000, 0, 2000, &device); 76 | ck_assert_int_eq(code, 0); 77 | complex float *output = NULL; 78 | size_t output_len = 0; 79 | device->sdr_process_rx(&output, &output_len, device->plugin); 80 | assert_complex_array(buffer, buffer_len, output, output_len); 81 | } 82 | 83 | END_TEST 84 | 85 | void teardown() { 86 | if (device != NULL) { 87 | device->destroy(device->plugin); 88 | free(device); 89 | device = NULL; 90 | } 91 | } 92 | 93 | void setup() { 94 | tmp_folder = getenv("TMPDIR"); 95 | if (tmp_folder == NULL) { 96 | tmp_folder = "/tmp"; 97 | } 98 | snprintf(filename, sizeof(filename), "%s/tx.cf32", tmp_folder); 99 | } 100 | 101 | Suite *common_suite(void) { 102 | Suite *s; 103 | TCase *tc_core; 104 | 105 | s = suite_create("file_source"); 106 | 107 | /* Core test case */ 108 | tc_core = tcase_create("Core"); 109 | 110 | tcase_add_test(tc_core, test_success); 111 | tcase_add_test(tc_core, test_rx_offset); 112 | tcase_add_test(tc_core, test_tx_invalid_arguments); 113 | tcase_add_test(tc_core, test_rx_invalid_arguments); 114 | 115 | tcase_add_checked_fixture(tc_core, setup, teardown); 116 | suite_add_tcase(s, tc_core); 117 | 118 | return s; 119 | } 120 | 121 | int main(void) { 122 | int number_failed; 123 | Suite *s; 124 | SRunner *sr; 125 | 126 | s = common_suite(); 127 | sr = srunner_create(s); 128 | 129 | srunner_set_fork_status(sr, CK_NOFORK); 130 | srunner_run_all(sr, CK_NORMAL); 131 | number_failed = srunner_ntests_failed(sr); 132 | srunner_free(sr); 133 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 134 | } 135 | -------------------------------------------------------------------------------- /src/dsp/fsk_demod.c: -------------------------------------------------------------------------------- 1 | #include "fsk_demod.h" 2 | #include "quadrature_demod.h" 3 | #include "clock_recovery_mm.h" 4 | #include "dc_blocker.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "lpf.h" 11 | 12 | #ifndef M_PI 13 | #define M_PI 3.14159265358979323846 14 | #endif 15 | 16 | struct fsk_demod_t { 17 | 18 | lpf *lpf1; 19 | lpf *lpf2; 20 | quadrature_demod *quad_demod; 21 | dc_blocker *dc; 22 | clock_mm *clock; 23 | 24 | int8_t *output; 25 | size_t output_len; 26 | }; 27 | 28 | int fsk_demod_create(uint64_t sampling_freq, uint32_t baud_rate, int64_t deviation, uint8_t decimation, uint32_t transition_width, bool use_dc_block, uint32_t max_input_buffer_length, fsk_demod **demod) { 29 | struct fsk_demod_t *result = malloc(sizeof(struct fsk_demod_t)); 30 | if (result == NULL) { 31 | return -ENOMEM; 32 | } 33 | // init all fields with 0 so that destroy_* method would work 34 | *result = (struct fsk_demod_t) {0}; 35 | 36 | double carson_cutoff = (double) llabs(deviation) + (double) baud_rate / 2; 37 | int code = lpf_create(1, sampling_freq, (uint64_t) carson_cutoff, (uint32_t) (0.1f * carson_cutoff), max_input_buffer_length, sizeof(float complex), &result->lpf1); 38 | if (code != 0) { 39 | fsk_demod_destroy(result); 40 | return code; 41 | } 42 | code = quadrature_demod_create((float) ((double) sampling_freq / (2 * M_PI * (double) deviation)), max_input_buffer_length, &result->quad_demod); 43 | if (code != 0) { 44 | fsk_demod_destroy(result); 45 | return code; 46 | } 47 | code = lpf_create(decimation, sampling_freq, baud_rate / 2, transition_width, max_input_buffer_length, sizeof(float), &result->lpf2); 48 | if (code != 0) { 49 | fsk_demod_destroy(result); 50 | return code; 51 | } 52 | 53 | float sps = (float) ((double) sampling_freq / baud_rate / decimation); 54 | 55 | if (use_dc_block) { 56 | code = dc_blocker_create((int) ceilf(sps * 32), &result->dc); 57 | if (code != 0) { 58 | fsk_demod_destroy(result); 59 | return code; 60 | } 61 | } 62 | 63 | code = clock_mm_create(sps, (sps * (float) M_PI) / 100, 0.5f, 0.5f / 8.0f, 0.01f, max_input_buffer_length, &result->clock); 64 | if (code != 0) { 65 | fsk_demod_destroy(result); 66 | return code; 67 | } 68 | 69 | result->output_len = max_input_buffer_length; 70 | result->output = malloc(sizeof(int8_t) * result->output_len); 71 | if (result->output == NULL) { 72 | fsk_demod_destroy(result); 73 | return -ENOMEM; 74 | } 75 | 76 | *demod = result; 77 | return 0; 78 | } 79 | 80 | void fsk_demod_process(const float complex *input, size_t input_len, int8_t **output, size_t *output_len, fsk_demod *demod) { 81 | float complex *lpf_output = NULL; 82 | size_t lpf_output_len = 0; 83 | lpf_process(input, input_len, (void **) &lpf_output, &lpf_output_len, demod->lpf1); 84 | 85 | float *qd_output = NULL; 86 | size_t qd_output_len = 0; 87 | quadrature_demod_process(lpf_output, lpf_output_len, &qd_output, &qd_output_len, demod->quad_demod); 88 | 89 | float *lpf2_output = NULL; 90 | size_t lpf2_output_len = 0; 91 | lpf_process(qd_output, qd_output_len, (void **) &lpf2_output, &lpf2_output_len, demod->lpf2); 92 | 93 | float *dc_output = NULL; 94 | size_t dc_output_len = 0; 95 | if (demod->dc != NULL) { 96 | dc_blocker_process(lpf2_output, lpf2_output_len, &dc_output, &dc_output_len, demod->dc); 97 | } else { 98 | dc_output = lpf2_output; 99 | dc_output_len = lpf2_output_len; 100 | } 101 | 102 | float *clock_output = NULL; 103 | size_t clock_output_len = 0; 104 | clock_mm_process(dc_output, dc_output_len, &clock_output, &clock_output_len, demod->clock); 105 | 106 | volk_32f_s32f_convert_8i(demod->output, clock_output, 127.0f, clock_output_len); 107 | 108 | *output = demod->output; 109 | *output_len = clock_output_len; 110 | } 111 | 112 | void fsk_demod_destroy(fsk_demod *demod) { 113 | if (demod == NULL) { 114 | return; 115 | } 116 | if (demod->lpf1 != NULL) { 117 | lpf_destroy(demod->lpf1); 118 | } 119 | if (demod->quad_demod != NULL) { 120 | quadrature_demod_destroy(demod->quad_demod); 121 | } 122 | if (demod->lpf2 != NULL) { 123 | lpf_destroy(demod->lpf2); 124 | } 125 | if (demod->dc != NULL) { 126 | dc_blocker_destroy(demod->dc); 127 | } 128 | if (demod->clock != NULL) { 129 | clock_mm_destroy(demod->clock); 130 | } 131 | if (demod->output != NULL) { 132 | free(demod->output); 133 | } 134 | free(demod); 135 | } 136 | -------------------------------------------------------------------------------- /test/test_fsk_demod.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../src/dsp/fsk_demod.h" 8 | #include "utils.h" 9 | 10 | fsk_demod *demod = NULL; 11 | uint8_t *buffer = NULL; 12 | FILE *input = NULL; 13 | FILE *expected = NULL; 14 | // buffer length is important here. do not change it 15 | // all test data was generated and verified with this buffer length 16 | // and VOLK_GENERIC=1 17 | // different buffer length or using SIMD can cause different precision when dealing with floats 18 | // this small precision issue can propagate further and cause small differences in the output. 19 | // i.e. instead of -31, it can produce -30 20 | uint32_t max_buffer_length = 4096; 21 | 22 | void assert_files_and_demod(const char *input_filename, const char *expected_filename) { 23 | input = fopen(input_filename, "rb"); 24 | ck_assert(input != NULL); 25 | expected = fopen(expected_filename, "rb"); 26 | ck_assert(expected != NULL); 27 | size_t buffer_len = sizeof(float complex) * max_buffer_length; 28 | buffer = malloc(sizeof(uint8_t) * buffer_len); 29 | ck_assert(buffer != NULL); 30 | size_t j = 0; 31 | while (true) { 32 | size_t actual_read = 0; 33 | int code = read_data(buffer, &actual_read, buffer_len, input); 34 | if (code != 0 && actual_read == 0) { 35 | break; 36 | } 37 | int8_t *output = NULL; 38 | size_t output_len = 0; 39 | fsk_demod_process((const complex float *) buffer, actual_read / 8, &output, &output_len, demod); 40 | code = read_data(buffer, &actual_read, output_len, expected); 41 | ck_assert_int_eq(code, 0); 42 | ck_assert_int_eq(actual_read, output_len); 43 | for (size_t i = 0; i < actual_read; i++) { 44 | // can't make test working across macbook, raspberrypi and travis 45 | // all of them have different float-precision issues 46 | // where results slightly different 47 | ck_assert(abs((int8_t) buffer[i] - output[i]) <= 2); 48 | } 49 | } 50 | } 51 | 52 | START_TEST(test_normal) { 53 | int code = fsk_demod_create(192000, 40000, 5000, 1, 2000, true, max_buffer_length, &demod); 54 | ck_assert_int_eq(code, 0); 55 | assert_files_and_demod("nusat.cf32", "processed.s8"); 56 | } 57 | 58 | END_TEST 59 | 60 | START_TEST(test_nan) { 61 | int code = fsk_demod_create(240000, 9600, 5000, 1, 2000, true, max_buffer_length, &demod); 62 | ck_assert_int_eq(code, 0); 63 | assert_files_and_demod("inputnan.cf32", "nan.s8"); 64 | } 65 | END_TEST 66 | 67 | START_TEST(test_handle_lucky7) { 68 | int code = fsk_demod_create(48000, 4800, 5000, 2, 2000, true, max_buffer_length, &demod); 69 | ck_assert_int_eq(code, 0); 70 | assert_files_and_demod("lucky7.expected.cf32", "lucky7.expected.s8"); 71 | } 72 | 73 | END_TEST 74 | 75 | START_TEST(test_no_dc) { 76 | int code = fsk_demod_create(48000, 4800, 5000, 2, 2000, false, max_buffer_length, &demod); 77 | ck_assert_int_eq(code, 0); 78 | assert_files_and_demod("lucky7.expected.cf32", "lucky7.expected.nodc.s8"); 79 | } 80 | 81 | END_TEST 82 | 83 | void teardown() { 84 | if (demod != NULL) { 85 | fsk_demod_destroy(demod); 86 | demod = NULL; 87 | } 88 | if (buffer != NULL) { 89 | free(buffer); 90 | buffer = NULL; 91 | } 92 | if (input != NULL) { 93 | fclose(input); 94 | input = NULL; 95 | } 96 | if (expected != NULL) { 97 | fclose(expected); 98 | expected = NULL; 99 | } 100 | } 101 | 102 | void setup() { 103 | //do nothing 104 | } 105 | 106 | Suite *common_suite(void) { 107 | Suite *s; 108 | TCase *tc_core; 109 | 110 | s = suite_create("fsk_demod"); 111 | 112 | /* Core test case */ 113 | tc_core = tcase_create("Core"); 114 | 115 | tcase_add_test(tc_core, test_normal); 116 | tcase_add_test(tc_core, test_nan); 117 | tcase_add_test(tc_core, test_handle_lucky7); 118 | tcase_add_test(tc_core, test_no_dc); 119 | 120 | tcase_add_checked_fixture(tc_core, setup, teardown); 121 | suite_add_tcase(s, tc_core); 122 | 123 | return s; 124 | } 125 | 126 | int main(void) { 127 | // this is especially important here 128 | // env variable is defined in run_tests.sh, but also here 129 | // to run this test from IDE 130 | setenv("VOLK_GENERIC", "1", 1); 131 | setenv("VOLK_ALIGNMENT", "16", 1); 132 | 133 | int number_failed; 134 | Suite *s; 135 | SRunner *sr; 136 | 137 | s = common_suite(); 138 | sr = srunner_create(s); 139 | 140 | srunner_set_fork_status(sr, CK_NOFORK); 141 | srunner_run_all(sr, CK_NORMAL); 142 | number_failed = srunner_ntests_failed(sr); 143 | srunner_free(sr); 144 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 145 | } -------------------------------------------------------------------------------- /src/sdr_worker.c: -------------------------------------------------------------------------------- 1 | #include "sdr_worker.h" 2 | #include "linked_list.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "sdr/sdr_server_client.h" 10 | 11 | struct sdr_worker_t { 12 | struct sdr_rx *rx; 13 | sdr_device *rx_device; 14 | uint32_t id; 15 | linked_list *dsp_configs; 16 | pthread_t sdr_thread; 17 | pthread_mutex_t mutex; 18 | }; 19 | 20 | struct array_t { 21 | float complex *output; 22 | size_t output_len; 23 | }; 24 | 25 | void sdr_worker_foreach_put(void *arg, void *data) { 26 | struct array_t *output = (struct array_t *) arg; 27 | dsp_worker *worker = (dsp_worker *) data; 28 | dsp_worker_put(output->output, output->output_len, worker); 29 | } 30 | 31 | static void *sdr_worker_callback(void *arg) { 32 | sdr_worker *worker = (sdr_worker *) arg; 33 | fprintf(stdout, "[%d] sdr_worker is starting\n", worker->id); 34 | struct array_t output; 35 | while (true) { 36 | int code = worker->rx_device->sdr_process_rx(&output.output, &output.output_len, worker->rx_device->plugin); 37 | if (code < -1) { 38 | // read timeout happened. it's ok. 39 | continue; 40 | } 41 | // terminate only when fully read from socket 42 | if (code != 0 && output.output_len == 0) { 43 | break; 44 | } 45 | pthread_mutex_lock(&worker->mutex); 46 | linked_list_foreach(&output, &sdr_worker_foreach_put, worker->dsp_configs); 47 | pthread_mutex_unlock(&worker->mutex); 48 | } 49 | //this would close all client sockets 50 | //and initiate cascade shutdown of sdr and dsp workers 51 | pthread_mutex_lock(&worker->mutex); 52 | linked_list_foreach(NULL, &dsp_worker_shutdown, worker->dsp_configs); 53 | pthread_mutex_unlock(&worker->mutex); 54 | return (void *) 0; 55 | } 56 | 57 | int sdr_worker_create(uint32_t id, struct sdr_rx *rx, sdr_device *rx_device, sdr_worker **worker) { 58 | struct sdr_worker_t *result = malloc(sizeof(struct sdr_worker_t)); 59 | if (result == NULL) { 60 | return -ENOMEM; 61 | } 62 | // init all fields with 0 so that destroy_* method would work 63 | *result = (struct sdr_worker_t) {0}; 64 | 65 | result->id = id; 66 | result->dsp_configs = NULL; 67 | result->rx = rx; 68 | result->mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; 69 | result->rx_device = rx_device; 70 | 71 | pthread_t sdr_thread; 72 | int code = pthread_create(&sdr_thread, NULL, &sdr_worker_callback, result); 73 | if (code != 0) { 74 | sdr_worker_destroy(result); 75 | return code; 76 | } 77 | result->sdr_thread = sdr_thread; 78 | 79 | *worker = result; 80 | return 0; 81 | } 82 | 83 | bool sdr_worker_find_closest(void *id, void *data) { 84 | if (data == NULL) { 85 | return false; 86 | } 87 | struct sdr_rx *rx = (struct sdr_rx *) id; 88 | sdr_worker *worker = (sdr_worker *) data; 89 | if (worker->rx->rx_center_freq == rx->rx_center_freq && 90 | worker->rx->rx_sampling_freq >= rx->rx_sampling_freq && 91 | worker->rx->rx_offset == rx->rx_offset) { 92 | return true; 93 | } 94 | return false; 95 | } 96 | 97 | void sdr_worker_destroy_by_dsp_worker_id(uint32_t id, sdr_worker *worker) { 98 | if (worker == NULL) { 99 | return; 100 | } 101 | pthread_mutex_lock(&worker->mutex); 102 | if (worker->dsp_configs != NULL) { 103 | linked_list_destroy_by_id(&id, &dsp_worker_find_by_id, &worker->dsp_configs); 104 | } 105 | bool result = (worker->dsp_configs == NULL); 106 | pthread_mutex_unlock(&worker->mutex); 107 | if (result) { 108 | sdr_worker_destroy(worker); 109 | } 110 | } 111 | 112 | void sdr_worker_destroy(void *data) { 113 | if (data == NULL) { 114 | return; 115 | } 116 | sdr_worker *worker = (sdr_worker *) data; 117 | //gracefully shutdown connection 118 | if (worker->rx_device != NULL) { 119 | worker->rx_device->stop_rx(worker->rx_device->plugin); 120 | } 121 | pthread_join(worker->sdr_thread, NULL); 122 | //destroy the rx device 123 | if (worker->rx_device != NULL) { 124 | worker->rx_device->destroy(worker->rx_device->plugin); 125 | free(worker->rx_device); 126 | } 127 | 128 | pthread_mutex_lock(&worker->mutex); 129 | if (worker->dsp_configs != NULL) { 130 | linked_list_destroy(worker->dsp_configs); 131 | } 132 | pthread_mutex_unlock(&worker->mutex); 133 | 134 | if (worker->rx != NULL) { 135 | free(worker->rx); 136 | } 137 | uint32_t id = worker->id; 138 | free(worker); 139 | fprintf(stdout, "[%d] sdr_worker stopped\n", id); 140 | } 141 | 142 | int sdr_worker_add_dsp_worker(dsp_worker *worker, sdr_worker *sdr) { 143 | pthread_mutex_lock(&sdr->mutex); 144 | int code = linked_list_add(worker, &dsp_worker_destroy, &sdr->dsp_configs); 145 | pthread_mutex_unlock(&sdr->mutex); 146 | return code; 147 | } -------------------------------------------------------------------------------- /test/test_queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../src/queue.h" 4 | #include "utils.h" 5 | 6 | queue *queue_obj = NULL; 7 | 8 | void take_from_buffer_and_assert(const float *expected, size_t expected_len) { 9 | float complex *result = NULL; 10 | size_t len = 0; 11 | take_buffer_for_processing(&result, &len, queue_obj); 12 | if (expected == NULL) { 13 | ck_assert(result == NULL); 14 | return; 15 | } 16 | ck_assert(result != NULL); 17 | assert_complex_array(expected, expected_len, result, len); 18 | complete_buffer_processing(queue_obj); 19 | } 20 | 21 | START_TEST(test_invalid_arguments) { 22 | int code = create_queue(4, 0, false, &queue_obj); 23 | ck_assert_int_eq(code, -1); 24 | 25 | code = create_queue(0, 10, false, &queue_obj); 26 | ck_assert_int_eq(code, -1); 27 | 28 | code = create_queue(4, 10, false, &queue_obj); 29 | ck_assert_int_eq(code, 0); 30 | 31 | // this should be ignored 32 | ck_assert_int_eq(-1, queue_put(NULL, 25, queue_obj)); 33 | 34 | const float buffer[2] = {1, 2}; 35 | ck_assert_int_eq(-1, queue_put((const float complex *) buffer, 0, queue_obj)); 36 | 37 | const float buffer2[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 38 | size_t buffer2_len = sizeof(buffer2) / sizeof(float) / 2; 39 | ck_assert_int_eq(-1, queue_put((const float complex *) buffer2, buffer2_len, queue_obj)); 40 | 41 | } 42 | 43 | END_TEST 44 | 45 | START_TEST (test_terminated_only_after_fully_processed) { 46 | int code = create_queue(262144, 10, false, &queue_obj); 47 | ck_assert_int_eq(code, 0); 48 | 49 | const float buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 50 | size_t buffer_len = sizeof(buffer) / sizeof(float) / 2; 51 | queue_put((const float complex *) buffer, buffer_len, queue_obj); 52 | 53 | interrupt_waiting_the_data(queue_obj); 54 | 55 | take_from_buffer_and_assert(buffer, buffer_len); 56 | take_from_buffer_and_assert(NULL, 0); 57 | 58 | //no-op 59 | interrupt_waiting_the_data(NULL); 60 | } 61 | 62 | END_TEST 63 | 64 | START_TEST (test_put_take) { 65 | int code = create_queue(262144, 10, false, &queue_obj); 66 | ck_assert_int_eq(code, 0); 67 | 68 | const float buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 69 | ck_assert_int_eq(0, queue_put((const float complex *) buffer, sizeof(buffer) / sizeof(float) / 2, queue_obj)); 70 | 71 | const float buffer2[2] = {1, 2}; 72 | ck_assert_int_eq(0, queue_put((const float complex *) buffer2, sizeof(buffer2) / sizeof(float) / 2, queue_obj)); 73 | 74 | take_from_buffer_and_assert(buffer, 10 / 2); 75 | take_from_buffer_and_assert(buffer2, 2 / 2); 76 | } 77 | 78 | END_TEST 79 | 80 | START_TEST (test_overflow) { 81 | int code = create_queue(262144, 1, false, &queue_obj); 82 | ck_assert_int_eq(code, 0); 83 | 84 | const float buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 85 | ck_assert_int_eq(0, queue_put((const float complex *) buffer, sizeof(buffer) / sizeof(float) / 2, queue_obj)); 86 | const float buffer2[10] = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; 87 | ck_assert_int_eq(0, queue_put((const float complex *) buffer2, sizeof(buffer2) / sizeof(float) / 2, queue_obj)); 88 | 89 | take_from_buffer_and_assert(buffer2, 10 / 2); 90 | } 91 | 92 | END_TEST 93 | 94 | START_TEST (test_putskipped) { 95 | int code = create_queue(262144, 1, true, &queue_obj); 96 | ck_assert_int_eq(code, 0); 97 | 98 | const float buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 99 | ck_assert_int_eq(0, queue_put((const float complex *) buffer, sizeof(buffer) / sizeof(float) / 2, queue_obj)); 100 | 101 | interrupt_waiting_the_data(queue_obj); 102 | 103 | // any put ignored after queue terminated 104 | ck_assert_int_eq(-1, queue_put((const float complex *) buffer, sizeof(buffer) / sizeof(float) / 2, queue_obj)); 105 | } 106 | 107 | END_TEST 108 | 109 | void teardown() { 110 | destroy_queue(queue_obj); 111 | } 112 | 113 | void setup() { 114 | //do nothing 115 | } 116 | 117 | Suite *common_suite(void) { 118 | Suite *s; 119 | TCase *tc_core; 120 | 121 | s = suite_create("queue"); 122 | 123 | /* Core test case */ 124 | tc_core = tcase_create("Core"); 125 | 126 | tcase_add_test(tc_core, test_put_take); 127 | tcase_add_test(tc_core, test_overflow); 128 | tcase_add_test(tc_core, test_terminated_only_after_fully_processed); 129 | tcase_add_test(tc_core, test_invalid_arguments); 130 | tcase_add_test(tc_core, test_putskipped); 131 | 132 | tcase_add_checked_fixture(tc_core, setup, teardown); 133 | suite_add_tcase(s, tc_core); 134 | 135 | return s; 136 | } 137 | 138 | int main(void) { 139 | int number_failed; 140 | Suite *s; 141 | SRunner *sr; 142 | 143 | s = common_suite(); 144 | sr = srunner_create(s); 145 | 146 | srunner_set_fork_status(sr, CK_NOFORK); 147 | srunner_run_all(sr, CK_NORMAL); 148 | number_failed = srunner_ntests_failed(sr); 149 | srunner_free(sr); 150 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /src/dsp/gfsk_mod.c: -------------------------------------------------------------------------------- 1 | #include "gfsk_mod.h" 2 | #include "gaussian_taps.h" 3 | #include 4 | #include 5 | #include 6 | #include "interp_fir_filter.h" 7 | #include "frequency_modulator.h" 8 | 9 | struct gfsk_mod_t { 10 | interp_fir_filter *filter; 11 | frequency_modulator *freq_mod; 12 | 13 | float *temp_input; 14 | size_t temp_input_len; 15 | }; 16 | 17 | int gfsk_mod_convolve(float *x, size_t x_len, float *y, size_t y_len, float **out, size_t *out_len) { 18 | size_t result_len = x_len + y_len - 1; 19 | float *result = malloc(sizeof(float) * result_len); 20 | if (result == NULL) { 21 | return -ENOMEM; 22 | } 23 | float *temp = malloc(sizeof(float) * result_len); 24 | if (temp == NULL) { 25 | return -ENOMEM; 26 | } 27 | memset(temp, 0, sizeof(float) * result_len); 28 | memcpy(temp, x, sizeof(float) * x_len); 29 | for (size_t i = 0; i < result_len; i++) { 30 | float sum = 0.0F; 31 | for (int j = 0, k = i; j < y_len && k >= 0; j++, k--) { 32 | sum += y[j] * temp[k]; 33 | } 34 | result[i] = sum; 35 | } 36 | free(temp); 37 | 38 | *out = result; 39 | *out_len = result_len; 40 | return 0; 41 | } 42 | 43 | int gfsk_mod_create(float samples_per_symbol, float sensitivity, float bt, uint32_t max_input_buffer_length, gfsk_mod **mod) { 44 | struct gfsk_mod_t *result = malloc(sizeof(struct gfsk_mod_t)); 45 | if (result == NULL) { 46 | return -ENOMEM; 47 | } 48 | // init all fields with 0 so that destroy_* method would work 49 | *result = (struct gfsk_mod_t) {0}; 50 | result->temp_input_len = max_input_buffer_length * 8; 51 | result->temp_input = malloc(sizeof(float) * result->temp_input_len); 52 | if (result->temp_input == NULL) { 53 | gfsk_mod_destroy(result); 54 | return -ENOMEM; 55 | } 56 | 57 | size_t gaussian_taps_len = 4 * samples_per_symbol; 58 | float *gaussian_taps = NULL; 59 | int code = gaussian_taps_create(1.0F, samples_per_symbol, bt, gaussian_taps_len, &gaussian_taps); 60 | if (code != 0) { 61 | gfsk_mod_destroy(result); 62 | return code; 63 | } 64 | size_t square_wave_len = (int) samples_per_symbol; 65 | float *square_wave = malloc(sizeof(float) * square_wave_len); 66 | if (square_wave == NULL) { 67 | free(gaussian_taps); 68 | gfsk_mod_destroy(result); 69 | return -ENOMEM; 70 | } 71 | for (size_t i = 0; i < square_wave_len; i++) { 72 | square_wave[i] = 1.0F; 73 | } 74 | 75 | float *taps = NULL; 76 | size_t taps_len = 0; 77 | code = gfsk_mod_convolve(gaussian_taps, gaussian_taps_len, square_wave, square_wave_len, &taps, &taps_len); 78 | free(gaussian_taps); 79 | free(square_wave); 80 | if (code != 0) { 81 | gfsk_mod_destroy(result); 82 | return code; 83 | } 84 | 85 | code = interp_fir_filter_create(taps, taps_len, (int) samples_per_symbol, result->temp_input_len, &result->filter); 86 | if (code != 0) { 87 | free(taps); 88 | gfsk_mod_destroy(result); 89 | return code; 90 | } 91 | 92 | code = frequency_modulator_create(sensitivity, (int) samples_per_symbol * result->temp_input_len, &result->freq_mod); 93 | if (code != 0) { 94 | gfsk_mod_destroy(result); 95 | return code; 96 | } 97 | 98 | *mod = result; 99 | return 0; 100 | } 101 | 102 | void gfsk_mod_process(const uint8_t *input, size_t input_len, float complex **output, size_t *output_len, gfsk_mod *mod) { 103 | if (input_len > mod->temp_input_len / 8) { 104 | fprintf(stderr, "<3>requested buffer %zu is more than max: %zu\n", input_len, mod->temp_input_len / 8); 105 | *output = NULL; 106 | *output_len = 0; 107 | return; 108 | } 109 | size_t temp_index = 0; 110 | for (size_t i = 0; i < input_len; i++) { 111 | for (int j = 0; j < 8; j++) { 112 | int bit = (input[i] >> (7 - j)) & 1; 113 | if (bit == 0) { 114 | mod->temp_input[temp_index] = -1.0F; 115 | } else { 116 | mod->temp_input[temp_index] = 1.0F; 117 | } 118 | temp_index++; 119 | } 120 | } 121 | 122 | float *filtered = NULL; 123 | size_t filtered_len = 0; 124 | interp_fir_filter_process(mod->temp_input, temp_index, &filtered, &filtered_len, mod->filter); 125 | 126 | float complex *modulated = NULL; 127 | size_t modulated_len = 0; 128 | frequency_modulator_process(filtered, filtered_len, &modulated, &modulated_len, mod->freq_mod); 129 | 130 | *output = modulated; 131 | *output_len = modulated_len; 132 | } 133 | 134 | void gfsk_mod_destroy(gfsk_mod *mod) { 135 | if (mod == NULL) { 136 | return; 137 | } 138 | if (mod->temp_input != NULL) { 139 | free(mod->temp_input); 140 | } 141 | if (mod->filter != NULL) { 142 | interp_fir_filter_destroy(mod->filter); 143 | } 144 | if (mod->freq_mod != NULL) { 145 | frequency_modulator_destroy(mod->freq_mod); 146 | } 147 | free(mod); 148 | } -------------------------------------------------------------------------------- /test/test_linked_list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../src/linked_list.h" 5 | 6 | linked_list *llist = NULL; 7 | 8 | struct sample_struct { 9 | int id; 10 | uint8_t *array; 11 | size_t array_len; 12 | }; 13 | 14 | void sample_struct_destroy(void *data) { 15 | struct sample_struct *test = (struct sample_struct *) data; 16 | if (test->array != NULL) { 17 | free(test->array); 18 | } 19 | free(test); 20 | } 21 | 22 | bool sample_struct_selector(void *arg, void *data) { 23 | int *id = (int *) arg; 24 | struct sample_struct *test = (struct sample_struct *) data; 25 | if (test->id == *id) { 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | void sample_struct_add(int id) { 32 | struct sample_struct *result = malloc(sizeof(struct sample_struct)); 33 | ck_assert(result != NULL); 34 | *result = (struct sample_struct) {0}; 35 | 36 | result->id = id; 37 | result->array_len = 8; 38 | result->array = malloc(sizeof(uint8_t) * result->array_len); 39 | ck_assert(result->array != NULL); 40 | 41 | int code = linked_list_add(result, &sample_struct_destroy, &llist); 42 | ck_assert_int_eq(code, 0); 43 | } 44 | 45 | bool sample_struct_selector_all(void *data) { 46 | return true; 47 | } 48 | 49 | bool sample_struct_selector_one(void *data) { 50 | struct sample_struct *test = (struct sample_struct *) data; 51 | if (test->id == 1) { 52 | return true; 53 | } 54 | return false; 55 | } 56 | 57 | void sample_struct_set_id(void *arg, void *data) { 58 | int *param = (int *) arg; 59 | struct sample_struct *test = (struct sample_struct *) data; 60 | test->id = *param; 61 | } 62 | 63 | START_TEST(test_remove_by_id) { 64 | sample_struct_add(1); 65 | int id = 8; 66 | void *result = linked_list_remove_by_id(&id, &sample_struct_selector, &llist); 67 | ck_assert(result == NULL); 68 | id = 1; 69 | result = linked_list_remove_by_id(&id, &sample_struct_selector, &llist); 70 | ck_assert(result != NULL); 71 | ck_assert(llist == NULL); 72 | sample_struct_destroy(result); 73 | } 74 | END_TEST 75 | 76 | START_TEST (test_foreach) { 77 | sample_struct_add(1); 78 | int id = 8; 79 | linked_list_foreach(&id, &sample_struct_set_id, llist); 80 | 81 | void *result = linked_list_find(&id, &sample_struct_selector, llist); 82 | ck_assert(result != NULL); 83 | struct sample_struct *actual = (struct sample_struct *) result; 84 | ck_assert_int_eq(id, actual->id); 85 | } 86 | 87 | END_TEST 88 | 89 | START_TEST (test_delete_by_selector) { 90 | sample_struct_add(1); 91 | sample_struct_add(2); 92 | linked_list_destroy_by_selector(&sample_struct_selector_all, &llist); 93 | ck_assert(llist == NULL); 94 | } 95 | 96 | END_TEST 97 | 98 | START_TEST (test_delete_by_selector1) { 99 | sample_struct_add(1); 100 | sample_struct_add(2); 101 | linked_list_destroy_by_selector(&sample_struct_selector_one, &llist); 102 | } 103 | 104 | END_TEST 105 | 106 | START_TEST (test_delete_last2) { 107 | sample_struct_add(1); 108 | sample_struct_add(2); 109 | int id = 1; 110 | linked_list_destroy_by_id(&id, &sample_struct_selector, &llist); 111 | id = 2; 112 | linked_list_destroy_by_id(&id, &sample_struct_selector, &llist); 113 | ck_assert(llist == NULL); 114 | } 115 | 116 | END_TEST 117 | 118 | START_TEST (test_delete_last) { 119 | sample_struct_add(1); 120 | int id = 1; 121 | linked_list_destroy_by_id(&id, &sample_struct_selector, &llist); 122 | ck_assert(llist == NULL); 123 | } 124 | 125 | END_TEST 126 | 127 | START_TEST (test_normal) { 128 | sample_struct_add(1); 129 | sample_struct_add(2); 130 | 131 | int id = 2; 132 | void *result = linked_list_find(&id, &sample_struct_selector, llist); 133 | ck_assert(result != NULL); 134 | struct sample_struct *actual = (struct sample_struct *) result; 135 | ck_assert_int_eq(id, actual->id); 136 | } 137 | 138 | END_TEST 139 | 140 | void teardown() { 141 | if (llist != NULL) { 142 | linked_list_destroy(llist); 143 | llist = NULL; 144 | } 145 | } 146 | 147 | void setup() { 148 | //do nothing 149 | } 150 | 151 | Suite *common_suite(void) { 152 | Suite *s; 153 | TCase *tc_core; 154 | 155 | s = suite_create("linked_list"); 156 | 157 | /* Core test case */ 158 | tc_core = tcase_create("Core"); 159 | 160 | tcase_add_test(tc_core, test_normal); 161 | tcase_add_test(tc_core, test_delete_last); 162 | tcase_add_test(tc_core, test_delete_last2); 163 | tcase_add_test(tc_core, test_delete_by_selector); 164 | tcase_add_test(tc_core, test_delete_by_selector1); 165 | tcase_add_test(tc_core, test_foreach); 166 | tcase_add_test(tc_core, test_remove_by_id); 167 | 168 | tcase_add_checked_fixture(tc_core, setup, teardown); 169 | suite_add_tcase(s, tc_core); 170 | 171 | return s; 172 | } 173 | 174 | int main(void) { 175 | int number_failed; 176 | Suite *s; 177 | SRunner *sr; 178 | 179 | s = common_suite(); 180 | sr = srunner_create(s); 181 | 182 | srunner_set_fork_status(sr, CK_NOFORK); 183 | srunner_run_all(sr, CK_NORMAL); 184 | number_failed = srunner_ntests_failed(sr); 185 | srunner_free(sr); 186 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 187 | } 188 | -------------------------------------------------------------------------------- /src/sgpsdp/sgp_math.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Unit SGP_Math 3 | * Author: Dr TS Kelso 4 | * Original Version: 1991 Oct 30 5 | * Current Revision: 1998 Mar 17 6 | * Version: 3.00 7 | * Copyright: 1991-1998, All Rights Reserved 8 | * 9 | * ported to C by: Neoklis Kyriazis April 9 2001 10 | */ 11 | 12 | #include "sgp4sdp4.h" 13 | 14 | /* Returns sign of a double */ 15 | int Sign(double arg) 16 | { 17 | if (arg > 0) 18 | return (1); 19 | else if (arg < 0) 20 | return (-1); 21 | else 22 | return (0); 23 | } 24 | 25 | /* Returns square of a double */ 26 | double Sqr(double arg) 27 | { 28 | return (arg * arg); 29 | } 30 | 31 | /* Returns cube of a double */ 32 | double Cube(double arg) 33 | { 34 | return (arg * arg * arg); 35 | } 36 | 37 | /* Returns angle in radians from arg id degrees */ 38 | double Radians(double arg) 39 | { 40 | return (arg * de2ra); 41 | } 42 | 43 | /* Returns angle in degrees from arg in rads */ 44 | double Degrees(double arg) 45 | { 46 | return (arg / de2ra); 47 | } 48 | 49 | /* Returns the arcsine of the argument */ 50 | double ArcSin(double arg) 51 | { 52 | if (fabs(arg) >= 1) 53 | return (Sign(arg) * pio2); 54 | else 55 | return (atan(arg / sqrt(1 - arg * arg))); 56 | } 57 | 58 | /* Returns orccosine of rgument */ 59 | double ArcCos(double arg) 60 | { 61 | return (pio2 - ArcSin(arg)); 62 | } 63 | 64 | /* Calculates scalar magnitude of a vector_t argument */ 65 | void Magnitude(vector_t * v) 66 | { 67 | v->w = sqrt(Sqr(v->x) + Sqr(v->y) + Sqr(v->z)); 68 | } 69 | 70 | /* Adds vectors v1 and v2 together to produce v3 */ 71 | void Vec_Add(vector_t * v1, vector_t * v2, vector_t * v3) 72 | { 73 | v3->x = v1->x + v2->x; 74 | v3->y = v1->y + v2->y; 75 | v3->z = v1->z + v2->z; 76 | 77 | Magnitude(v3); 78 | } 79 | 80 | /* Subtracts vector v2 from v1 to produce v3 */ 81 | void Vec_Sub(vector_t * v1, vector_t * v2, vector_t * v3) 82 | { 83 | v3->x = v1->x - v2->x; 84 | v3->y = v1->y - v2->y; 85 | v3->z = v1->z - v2->z; 86 | 87 | Magnitude(v3); 88 | } 89 | 90 | /* Multiplies the vector v1 by the scalar k to produce the vector v2 */ 91 | void Scalar_Multiply(double k, vector_t * v1, vector_t * v2) 92 | { 93 | v2->x = k * v1->x; 94 | v2->y = k * v1->y; 95 | v2->z = k * v1->z; 96 | v2->w = fabs(k) * v1->w; 97 | } 98 | 99 | /* Multiplies the vector v1 by the scalar k */ 100 | void Scale_Vector(double k, vector_t * v) 101 | { 102 | v->x *= k; 103 | v->y *= k; 104 | v->z *= k; 105 | Magnitude(v); 106 | } 107 | 108 | /* Returns the dot product of two vectors */ 109 | double Dot(vector_t * v1, vector_t * v2) 110 | { 111 | return (v1->x * v2->x + v1->y * v2->y + v1->z * v2->z); 112 | } 113 | 114 | /* Calculates the angle between vectors v1 and v2 */ 115 | double Angle(vector_t * v1, vector_t * v2) 116 | { 117 | Magnitude(v1); 118 | Magnitude(v2); 119 | return (ArcCos(Dot(v1, v2) / (v1->w * v2->w))); 120 | } 121 | 122 | /* Produces cross product of v1 and v2, and returns in v3 */ 123 | void Cross(vector_t * v1, vector_t * v2, vector_t * v3) 124 | { 125 | v3->x = v1->y * v2->z - v1->z * v2->y; 126 | v3->y = v1->z * v2->x - v1->x * v2->z; 127 | v3->z = v1->x * v2->y - v1->y * v2->x; 128 | Magnitude(v3); 129 | } 130 | 131 | /* Normalizes a vector */ 132 | void Normalize(vector_t * v) 133 | { 134 | v->x /= v->w; 135 | v->y /= v->w; 136 | v->z /= v->w; 137 | } 138 | 139 | /* Four-quadrant arctan function */ 140 | double AcTan(double sinx, double cosx) 141 | { 142 | if (cosx == 0) 143 | { 144 | if (sinx > 0) 145 | return (pio2); 146 | else 147 | return (x3pio2); 148 | } 149 | else 150 | { 151 | if (cosx > 0) 152 | { 153 | if (sinx > 0) 154 | return (atan(sinx / cosx)); 155 | else 156 | return (twopi + atan(sinx / cosx)); 157 | } 158 | else 159 | return (pi + atan(sinx / cosx)); 160 | } 161 | 162 | } 163 | 164 | /* Returns mod 2pi of argument */ 165 | double FMod2p(double x) 166 | { 167 | int i; 168 | double ret_val; 169 | 170 | ret_val = x; 171 | i = ret_val / twopi; 172 | ret_val -= i * twopi; 173 | if (ret_val < 0) 174 | ret_val += twopi; 175 | 176 | return (ret_val); 177 | } 178 | 179 | /* Returns arg1 mod arg2 */ 180 | double Modulus(double arg1, double arg2) 181 | { 182 | int i; 183 | double ret_val; 184 | 185 | ret_val = arg1; 186 | i = ret_val / arg2; 187 | ret_val -= i * arg2; 188 | if (ret_val < 0) 189 | ret_val += arg2; 190 | 191 | return (ret_val); 192 | } 193 | 194 | /* Returns fractional part of double argument */ 195 | double Frac(double arg) 196 | { 197 | return (arg - floor(arg)); 198 | } 199 | 200 | /* Returns argument rounded up to nearest integer */ 201 | int Round(double arg) 202 | { 203 | return ((int)floor(arg + 0.5)); 204 | } 205 | 206 | /* Returns the floor integer of a double arguement, as double */ 207 | double Int(double arg) 208 | { 209 | return (floor(arg)); 210 | } 211 | 212 | /* Converts the satellite's position and velocity */ 213 | /* vectors from normalised values to km and km/sec */ 214 | void Convert_Sat_State(vector_t * pos, vector_t * vel) 215 | { 216 | Scale_Vector(xkmper, pos); 217 | Scale_Vector(xkmper * xmnpda / secday, vel); 218 | } 219 | -------------------------------------------------------------------------------- /src/dsp/clock_recovery_mm.c: -------------------------------------------------------------------------------- 1 | #include "clock_recovery_mm.h" 2 | #include "mmse_fir_interpolator.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct clock_mm_t { 10 | mmse_fir_interpolator *interp; 11 | float omega; 12 | float omega_mid; 13 | float omega_lim; 14 | float gain_omega; 15 | float mu; 16 | float gain_mu; 17 | float omega_relative_limit; 18 | float last_sample; 19 | 20 | float *working_buffer; 21 | size_t history_offset; 22 | size_t working_len_total; 23 | 24 | float *output; 25 | size_t output_len; 26 | }; 27 | 28 | int 29 | clock_mm_create(float omega, float gain_omega, float mu, float gain_mu, float omega_relative_limit, size_t output_len, 30 | clock_mm **clock) { 31 | struct clock_mm_t *result = malloc(sizeof(struct clock_mm_t)); 32 | if (result == NULL) { 33 | return -ENOMEM; 34 | } 35 | // init all fields with 0 so that destroy_* method would work 36 | *result = (struct clock_mm_t) {0}; 37 | result->mu = mu; 38 | result->omega = omega; 39 | result->gain_omega = gain_omega; 40 | result->gain_mu = gain_mu; 41 | result->omega_relative_limit = omega_relative_limit; 42 | result->omega_mid = omega; 43 | result->omega_lim = result->omega_mid * result->omega_relative_limit; 44 | 45 | result->last_sample = 0.0F; 46 | int code = mmse_fir_interpolator_create(&result->interp); 47 | if (code != 0) { 48 | clock_mm_destroy(result); 49 | return code; 50 | } 51 | result->output_len = output_len; 52 | result->output = malloc(sizeof(float) * output_len); 53 | if (result->output == NULL) { 54 | clock_mm_destroy(result); 55 | return -ENOMEM; 56 | } 57 | result->history_offset = 0; 58 | result->working_len_total = output_len + mmse_fir_interpolator_taps(result->interp); 59 | result->working_buffer = volk_malloc(sizeof(float) * result->working_len_total, volk_get_alignment()); 60 | if (result->working_buffer == NULL) { 61 | clock_mm_destroy(result); 62 | return -ENOMEM; 63 | } 64 | memset(result->working_buffer, 0, sizeof(float) * result->working_len_total); 65 | 66 | *clock = result; 67 | return 0; 68 | } 69 | 70 | float slice(float x) { 71 | return x < 0 ? -1.0F : 1.0F; 72 | } 73 | 74 | static inline float branchless_clip(float x, float clip) { 75 | return 0.5F * (fabsf(x + clip) - fabsf(x - clip)); 76 | } 77 | 78 | void clock_mm_process(const float *input, size_t input_len, float **output, size_t *output_len, clock_mm *clock) { 79 | // even if mu == 1 input buffer should not be more than output buffer 80 | if (input_len > clock->output_len) { 81 | fprintf(stderr, "<3>requested buffer %zu is more than max: %zu\n", input_len, clock->output_len); 82 | *output = NULL; 83 | *output_len = 0; 84 | return; 85 | } 86 | // prepend history to the input 87 | memcpy(clock->working_buffer + clock->history_offset, input, input_len * sizeof(float)); 88 | int taps_len = mmse_fir_interpolator_taps(clock->interp); 89 | int ii = 0; // input index 90 | int oo = 0; // output index 91 | int previous = 0; 92 | size_t working_len = clock->history_offset + input_len; 93 | // not enough input 94 | if (working_len < taps_len) { 95 | clock->history_offset = working_len; 96 | *output = NULL; 97 | *output_len = 0; 98 | return; 99 | } 100 | size_t max_index = working_len - (taps_len - 1); 101 | float mm_val; 102 | 103 | while (ii < max_index && oo < clock->output_len) { 104 | // produce output sample 105 | clock->output[oo] = mmse_fir_interpolator_process(clock->working_buffer + ii, clock->mu, 106 | clock->interp); 107 | if (isnan(clock->output[oo])) { 108 | clock->output[oo] = 0.0f; 109 | previous = ii; 110 | ii += (int) floorf(clock->omega); 111 | oo++; 112 | continue; 113 | } 114 | 115 | mm_val = slice(clock->last_sample) * clock->output[oo] - slice(clock->output[oo]) * clock->last_sample; 116 | clock->last_sample = clock->output[oo]; 117 | previous = ii; 118 | 119 | clock->omega = clock->omega + clock->gain_omega * mm_val; 120 | clock->omega = clock->omega_mid + branchless_clip(clock->omega - clock->omega_mid, clock->omega_lim); 121 | clock->mu = clock->mu + clock->omega + clock->gain_mu * mm_val; 122 | ii += (int) floorf(clock->mu); 123 | clock->mu = clock->mu - floorf(clock->mu); 124 | oo++; 125 | } 126 | 127 | size_t last_index; 128 | if (ii > working_len) { 129 | last_index = previous; 130 | } else { 131 | last_index = ii; 132 | } 133 | clock->history_offset = working_len - last_index; 134 | 135 | memmove(clock->working_buffer, clock->working_buffer + last_index, sizeof(float) * clock->history_offset); 136 | 137 | *output = clock->output; 138 | *output_len = oo; 139 | } 140 | 141 | void clock_mm_destroy(clock_mm *clock) { 142 | if (clock == NULL) { 143 | return; 144 | } 145 | if (clock->interp != NULL) { 146 | mmse_fir_interpolator_destroy(clock->interp); 147 | } 148 | if (clock->output != NULL) { 149 | free(clock->output); 150 | } 151 | if (clock->working_buffer != NULL) { 152 | volk_free(clock->working_buffer); 153 | } 154 | free(clock); 155 | } 156 | -------------------------------------------------------------------------------- /test/test_sgp4_002.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ 2 | /* 3 | Gpredict: Real-time satellite tracking and orbit prediction program 4 | 5 | Copyright (C) 2001-2008 Alexandru Csete. 6 | 7 | Comments, questions and bugreports should be submitted via 8 | http://sourceforge.net/projects/gpredict/ 9 | More details can be found at the project home page: 10 | 11 | http://gpredict.oz9aec.net/ 12 | 13 | This program is free software; you can redistribute it and/or modify 14 | it under the terms of the GNU General Public License as published by 15 | the Free Software Foundation; either version 2 of the License, or 16 | (at your option) any later version. 17 | 18 | This program is distributed in the hope that it will be useful, 19 | but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | GNU General Public License for more details. 22 | 23 | You should have received a copy of the GNU General Public License 24 | along with this program; if not, write to the 25 | Free Software Foundation, Inc., 26 | 59 Temple Place, Suite 330, 27 | Boston, MA 02111-1307 28 | USA 29 | */ 30 | /* Unit test for SGP4 */ 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "../src/sgpsdp/sgp4sdp4.h" 37 | 38 | #define TEST_STEPS 5 39 | 40 | /* structure to hold a set of data */ 41 | typedef struct { 42 | double t; 43 | double x; 44 | double y; 45 | double z; 46 | double vx; 47 | double vy; 48 | double vz; 49 | } dataset_t; 50 | 51 | 52 | const dataset_t expected[TEST_STEPS] = { 53 | {0.0, 54 | 7473.37235249, 428.95458268, 5828.74803892, 55 | 5.1071513, 6.44468284, -0.18613096}, 56 | {360.0, 57 | -3305.22249435, 32410.86724220, -24697.17847749, 58 | -1.30113538, -1.15131518, -0.28333528}, 59 | {720.0, 60 | 14271.28902792, 24110.45647174, -4725.76149170, 61 | -0.32050445, 2.67984074, -2.08405289}, 62 | {1080.0, 63 | -9990.05125819, 22717.38011629, -23616.90130945, 64 | -1.01667246, -2.29026759, 0.72892364}, 65 | {1440.0, 66 | 9787.88496660, 33753.34020891, -15030.79330940, 67 | -1.09424947, 0.92358845, -1.52230928} 68 | }; 69 | 70 | 71 | char tle_str[3][80]; 72 | sat_t sat; 73 | FILE *fp = NULL; 74 | 75 | START_TEST(test_stipping_spaces2) { 76 | char tle_with_spaces[3][80] = {" ", "1 44406U 19038W 20069.88080907 .00000505 00000-0 32890-4 0 9992", "2 44406 97.5270 32.5584 0026284 107.4758 252.9348 15.12089395 37524"}; 77 | sat_t result; 78 | int code = Get_Next_Tle_Set(tle_with_spaces, &result.tle); 79 | ck_assert_int_eq(code, 1); 80 | ck_assert_str_eq("", result.tle.sat_name); 81 | } 82 | END_TEST 83 | 84 | START_TEST(test_stipping_spaces) { 85 | char tle_with_spaces[3][80] = {"01234567890123456789123 ", "1 44406U 19038W 20069.88080907 .00000505 00000-0 32890-4 0 9992", "2 44406 97.5270 32.5584 0026284 107.4758 252.9348 15.12089395 37524"}; 86 | sat_t result; 87 | int code = Get_Next_Tle_Set(tle_with_spaces, &result.tle); 88 | ck_assert_int_eq(code, 1); 89 | ck_assert_str_eq("01234567890123456789123", result.tle.sat_name); 90 | } 91 | END_TEST 92 | 93 | START_TEST(test_normal) { 94 | 95 | int i; 96 | 97 | /* read tle file */ 98 | fp = fopen("test-002.tle", "r"); 99 | ck_assert(fp != NULL); 100 | char *code = fgets(tle_str[0], 80, fp); 101 | ck_assert(code != NULL); 102 | code = fgets(tle_str[1], 80, fp); 103 | ck_assert(code != NULL); 104 | code = fgets(tle_str[2], 80, fp); 105 | ck_assert(code != NULL); 106 | int ret_code = Get_Next_Tle_Set(tle_str, &sat.tle); 107 | ck_assert_int_eq(ret_code, 1); 108 | 109 | select_ephemeris(&sat); 110 | 111 | for (i = 0; i < TEST_STEPS; i++) { 112 | SDP4(&sat, expected[i].t); 113 | Convert_Sat_State(&sat.pos, &sat.vel); 114 | 115 | ck_assert(fabsl(sat.pos.x - expected[i].x) < 0.00001); 116 | ck_assert(fabsl(sat.pos.y - expected[i].y) < 0.00001); 117 | ck_assert(fabsl(sat.pos.z - expected[i].z) < 0.00001); 118 | ck_assert(fabsl(sat.vel.x - expected[i].vx) < 0.00001); 119 | ck_assert(fabsl(sat.vel.y - expected[i].vy) < 0.00001); 120 | ck_assert(fabsl(sat.vel.z - expected[i].vz) < 0.00001); 121 | } 122 | 123 | } 124 | END_TEST 125 | 126 | void teardown() { 127 | if (fp != NULL) { 128 | fclose(fp); 129 | fp = NULL; 130 | } 131 | } 132 | 133 | void setup() { 134 | //do nothing 135 | } 136 | 137 | Suite *common_suite(void) { 138 | Suite *s; 139 | TCase *tc_core; 140 | 141 | s = suite_create("sgp4_002"); 142 | 143 | /* Core test case */ 144 | tc_core = tcase_create("Core"); 145 | 146 | tcase_add_test(tc_core, test_normal); 147 | tcase_add_test(tc_core, test_stipping_spaces); 148 | tcase_add_test(tc_core, test_stipping_spaces2); 149 | 150 | tcase_add_checked_fixture(tc_core, setup, teardown); 151 | suite_add_tcase(s, tc_core); 152 | 153 | return s; 154 | } 155 | 156 | int main(void) { 157 | int number_failed; 158 | Suite *s; 159 | SRunner *sr; 160 | 161 | s = common_suite(); 162 | sr = srunner_create(s); 163 | 164 | srunner_set_fork_status(sr, CK_NOFORK); 165 | srunner_run_all(sr, CK_NORMAL); 166 | number_failed = srunner_ntests_failed(sr); 167 | srunner_free(sr); 168 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 169 | } -------------------------------------------------------------------------------- /src/dsp/interp_fir_filter.c: -------------------------------------------------------------------------------- 1 | #include "interp_fir_filter.h" 2 | #include 3 | #include 4 | #include "fir_filter.h" 5 | 6 | #include 7 | 8 | struct interp_fir_filter_t { 9 | float *taps; 10 | size_t taps_len; 11 | 12 | fir_filter **filters; 13 | size_t filters_len; 14 | 15 | float *output; 16 | size_t output_len; 17 | }; 18 | 19 | int interp_fir_filter_roundup_to_interpolation(float *taps, size_t taps_len, uint8_t interpolation, float **result_taps, size_t *result_taps_len) { 20 | size_t len; 21 | // round up length to a multiple of the interpolation factor 22 | size_t n = taps_len % interpolation; 23 | if (n > 0) { 24 | n = interpolation - n; 25 | len = taps_len + n; 26 | } else { 27 | len = taps_len; 28 | } 29 | 30 | float *result = malloc(sizeof(float) * len); 31 | if (result == NULL) { 32 | return -ENOMEM; 33 | } 34 | memset(result, 0, sizeof(float) * len); 35 | memcpy(result, taps, sizeof(float) * taps_len); 36 | 37 | *result_taps = result; 38 | *result_taps_len = len; 39 | return 0; 40 | } 41 | 42 | float **interp_fir_filter_create_interleaved_taps(const float *taps, size_t taps_len, uint8_t interpolation) { 43 | // guaranteed to be integer. see interp_fir_filter_roundup_to_interpolation 44 | size_t filter_taps_len = taps_len / interpolation; 45 | float **interleaved_taps = malloc(sizeof(float *) * interpolation); 46 | if (interleaved_taps == NULL) { 47 | return NULL; 48 | } 49 | *interleaved_taps = (float *) {0}; 50 | 51 | //init individual taps so that they can be freed by the corresponding filter 52 | int result_code = 0; 53 | for (uint8_t i = 0; i < interpolation; i++) { 54 | interleaved_taps[i] = malloc(sizeof(float) * filter_taps_len); 55 | if (interleaved_taps[i] == NULL) { 56 | result_code = -ENOMEM; 57 | } 58 | } 59 | if (result_code != 0) { 60 | for (uint8_t i = 0; i < interpolation; i++) { 61 | if (interleaved_taps[i] != NULL) { 62 | free(interleaved_taps[i]); 63 | } 64 | } 65 | free(interleaved_taps); 66 | return NULL; 67 | } 68 | 69 | for (size_t i = 0; i < taps_len; i++) { 70 | interleaved_taps[i % interpolation][i / interpolation] = taps[i]; 71 | } 72 | return interleaved_taps; 73 | } 74 | 75 | int interp_fir_filter_create(float *taps, size_t taps_len, uint8_t interpolation, uint32_t max_input_buffer_length, interp_fir_filter **filter) { 76 | struct interp_fir_filter_t *result = malloc(sizeof(struct interp_fir_filter_t)); 77 | if (result == NULL) { 78 | return -ENOMEM; 79 | } 80 | // init all fields with 0 so that destroy_* method would work 81 | *result = (struct interp_fir_filter_t) {0}; 82 | 83 | result->output_len = max_input_buffer_length * interpolation; 84 | result->output = malloc(sizeof(float) * result->output_len); 85 | if (result->output == NULL) { 86 | interp_fir_filter_destroy(result); 87 | return -ENOMEM; 88 | } 89 | 90 | int code = interp_fir_filter_roundup_to_interpolation(taps, taps_len, interpolation, &result->taps, &result->taps_len); 91 | if (code != 0) { 92 | interp_fir_filter_destroy(result); 93 | return code; 94 | } 95 | 96 | float **interleaved_taps = interp_fir_filter_create_interleaved_taps(result->taps, result->taps_len, interpolation); 97 | if (interleaved_taps == NULL) { 98 | interp_fir_filter_destroy(result); 99 | return -ENOMEM; 100 | } 101 | 102 | result->filters_len = interpolation; 103 | result->filters = malloc(sizeof(fir_filter *) * result->filters_len); 104 | if (result->filters == NULL) { 105 | for (uint8_t i = 0; i < interpolation; i++) { 106 | free(interleaved_taps[i]); 107 | } 108 | free(interleaved_taps); 109 | interp_fir_filter_destroy(result); 110 | return -ENOMEM; 111 | } 112 | 113 | int result_code = 0; 114 | size_t filter_taps_len = result->taps_len / interpolation; 115 | for (size_t i = 0; i < result->filters_len; i++) { 116 | fir_filter *curFilter = NULL; 117 | code = fir_filter_create(1, interleaved_taps[i], filter_taps_len, max_input_buffer_length, sizeof(float), &curFilter); 118 | //init all filters anyway 119 | if (code != 0) { 120 | free(interleaved_taps[i]); 121 | result_code = code; 122 | result->filters[i] = NULL; 123 | } else { 124 | result->filters[i] = curFilter; 125 | } 126 | } 127 | //release array of pointers and not taps themselves 128 | free(interleaved_taps); 129 | if (result_code != 0) { 130 | interp_fir_filter_destroy(result); 131 | return result_code; 132 | } 133 | 134 | free(taps); 135 | *filter = result; 136 | return 0; 137 | } 138 | 139 | void interp_fir_filter_process(float *input, size_t input_len, float **output, size_t *output_len, interp_fir_filter *filter) { 140 | size_t result_len = 0; 141 | for (size_t i = 0; i < filter->filters_len; i++) { 142 | float *cur_output = NULL; 143 | size_t cur_output_len = 0; 144 | fir_filter_process(input, input_len, (void **) &cur_output, &cur_output_len, filter->filters[i]); 145 | result_len = filter->filters_len * cur_output_len; 146 | //de-interleave results 147 | for (size_t j = i, k = 0; j < result_len && k < cur_output_len; j += filter->filters_len, k++) { 148 | filter->output[j] = cur_output[k]; 149 | } 150 | } 151 | 152 | *output_len = result_len; 153 | *output = filter->output; 154 | } 155 | 156 | void interp_fir_filter_destroy(interp_fir_filter *filter) { 157 | if (filter == NULL) { 158 | return; 159 | } 160 | if (filter->taps != NULL) { 161 | free(filter->taps); 162 | } 163 | if (filter->filters != NULL) { 164 | for (size_t i = 0; i < filter->filters_len; i++) { 165 | fir_filter_destroy(filter->filters[i]); 166 | } 167 | free(filter->filters); 168 | } 169 | if (filter->output != NULL) { 170 | free(filter->output); 171 | } 172 | free(filter); 173 | } -------------------------------------------------------------------------------- /src/sdr/file_source.c: -------------------------------------------------------------------------------- 1 | #include "file_source.h" 2 | #include "../dsp/sig_source.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct file_device_t { 9 | uint32_t id; 10 | 11 | FILE *rx_file; 12 | FILE *tx_file; 13 | 14 | int64_t freq_offset; 15 | sig_source *signal; 16 | 17 | float complex *output; 18 | size_t output_len; 19 | 20 | bool running; 21 | pthread_mutex_t mutex; 22 | pthread_cond_t condition; 23 | }; 24 | 25 | void file_source_stop(void *plugin) { 26 | file_device *device = (file_device *) plugin; 27 | pthread_mutex_lock(&device->mutex); 28 | device->running = false; 29 | pthread_cond_broadcast(&device->condition); 30 | pthread_mutex_unlock(&device->mutex); 31 | } 32 | 33 | int file_source_create(uint32_t id, const char *rx_filename, const char *tx_filename, uint64_t sampling_freq, int64_t freq_offset, uint32_t max_output_buffer_length, sdr_device **output) { 34 | struct file_device_t *device = malloc(sizeof(struct file_device_t)); 35 | if (device == NULL) { 36 | return -ENOMEM; 37 | } 38 | *device = (struct file_device_t) {0}; 39 | device->id = id; 40 | device->freq_offset = freq_offset; 41 | device->output_len = max_output_buffer_length; 42 | device->output = malloc(sizeof(float complex) * device->output_len); 43 | if (device->output == NULL) { 44 | file_source_destroy(device); 45 | return -ENOMEM; 46 | } 47 | device->condition = (pthread_cond_t) PTHREAD_COND_INITIALIZER; 48 | device->mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; 49 | device->running = true; 50 | 51 | if (freq_offset != 0) { 52 | int code = sig_source_create(1.0F, sampling_freq, max_output_buffer_length, &device->signal); 53 | if (code != 0) { 54 | fprintf(stderr, "<3>[%d] unable to create signal generator\n", device->id); 55 | file_source_destroy(device); 56 | return -1; 57 | } 58 | } 59 | 60 | if (rx_filename != NULL) { 61 | device->rx_file = fopen(rx_filename, "rb"); 62 | if (device->rx_file == NULL) { 63 | fprintf(stderr, "<3>[%d] unable to open file for input: %s\n", device->id, rx_filename); 64 | file_source_destroy(device); 65 | return -1; 66 | } 67 | } 68 | 69 | if (tx_filename != NULL) { 70 | device->tx_file = fopen(tx_filename, "wb"); 71 | if (device->tx_file == NULL) { 72 | fprintf(stderr, "<3>[%d] unable to open file for output: %s\n", device->id, tx_filename); 73 | file_source_destroy(device); 74 | return -1; 75 | } 76 | } 77 | 78 | struct sdr_device_t *result = malloc(sizeof(struct sdr_device_t)); 79 | if (result == NULL) { 80 | file_source_destroy(device); 81 | return -ENOMEM; 82 | } 83 | result->plugin = device; 84 | result->destroy = file_source_destroy; 85 | result->sdr_process_rx = file_source_process_rx; 86 | result->sdr_process_tx = file_source_process_tx; 87 | result->stop_rx = file_source_stop; 88 | 89 | *output = result; 90 | return 0; 91 | } 92 | 93 | int file_source_process_rx(float complex **output, size_t *output_len, void *plugin) { 94 | file_device *device = (file_device *) plugin; 95 | if (device->rx_file == NULL) { 96 | fprintf(stderr, "<3>[%d] rx file was not initialized\n", device->id); 97 | *output = NULL; 98 | *output_len = 0; 99 | return -1; 100 | } 101 | size_t actually_read = fread(device->output, sizeof(float complex), device->output_len, device->rx_file); 102 | if (actually_read == 0) { 103 | // on any error terminate early 104 | if (ferror(device->rx_file) != 0) { 105 | *output = NULL; 106 | *output_len = 0; 107 | return -1; 108 | } 109 | // wait until client disconnects 110 | pthread_mutex_lock(&device->mutex); 111 | while (device->running) { 112 | pthread_cond_wait(&device->condition, &device->mutex); 113 | } 114 | pthread_mutex_unlock(&device->mutex); 115 | *output = NULL; 116 | *output_len = 0; 117 | return -1; 118 | } 119 | if (device->signal != NULL) { 120 | float complex *sig_output = NULL; 121 | size_t sig_output_len = 0; 122 | sig_source_multiply(device->freq_offset, device->output, actually_read, &sig_output, &sig_output_len, device->signal); 123 | *output = sig_output; 124 | *output_len = sig_output_len; 125 | } else { 126 | *output = device->output; 127 | *output_len = actually_read; 128 | } 129 | return 0; 130 | } 131 | 132 | int file_source_process_tx(float complex *input, size_t input_len, void *plugin) { 133 | file_device *device = (file_device *) plugin; 134 | if (input_len > device->output_len) { 135 | fprintf(stderr, "<3>requested buffer %zu is more than max: %zu\n", input_len, device->output_len); 136 | return -1; 137 | } 138 | if (device->tx_file == NULL) { 139 | fprintf(stderr, "<3>[%d] tx file was not initialized\n", device->id); 140 | return -1; 141 | } 142 | if (device->signal != NULL) { 143 | float complex *sig_output = NULL; 144 | size_t sig_output_len = 0; 145 | sig_source_multiply(device->freq_offset, input, input_len, &sig_output, &sig_output_len, device->signal); 146 | //ignore actually written 147 | fwrite(sig_output, sizeof(float complex), sig_output_len, device->tx_file); 148 | } else { 149 | //ignore actually written 150 | fwrite(input, sizeof(float complex), input_len, device->tx_file); 151 | } 152 | return 0; 153 | } 154 | 155 | void file_source_destroy(void *plugin) { 156 | if (plugin == NULL) { 157 | return; 158 | } 159 | file_device *device = (file_device *) plugin; 160 | if (device->rx_file != NULL) { 161 | fclose(device->rx_file); 162 | } 163 | if (device->tx_file != NULL) { 164 | fclose(device->tx_file); 165 | } 166 | if (device->output != NULL) { 167 | free(device->output); 168 | } 169 | if (device->signal != NULL) { 170 | sig_source_destroy(device->signal); 171 | } 172 | free(device); 173 | } -------------------------------------------------------------------------------- /test/test_doppler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../src/dsp/doppler.h" 6 | #include "utils.h" 7 | #include 8 | 9 | FILE *input_file = NULL; 10 | uint8_t *input_buffer = NULL; 11 | FILE *expected_file = NULL; 12 | uint8_t *expected_buffer = NULL; 13 | doppler *dopp = NULL; 14 | char tle[3][80] = {"LUCKY-7", "1 44406U 19038W 20069.88080907 .00000505 00000-0 32890-4 0 9992", "2 44406 97.5270 32.5584 0026284 107.4758 252.9348 15.12089395 37524"}; 15 | int max_buffer_length = 2000; 16 | 17 | START_TEST (test_invalid_arguments) { 18 | int code = doppler_create(53.72F, 47.57F, 0.0F, 48000, 437525000, 0, 1583840449, max_buffer_length, tle, &dopp); 19 | ck_assert_int_eq(code, 0); 20 | 21 | float complex *output = NULL; 22 | size_t output_len = 0; 23 | doppler_process_rx(NULL, 12, &output, &output_len, dopp); 24 | ck_assert(output == NULL); 25 | 26 | const float buffer[2] = {1, 2}; 27 | doppler_process_rx((float complex *) buffer, 0, &output, &output_len, dopp); 28 | ck_assert(output == NULL); 29 | 30 | size_t input_buffer_len = max_buffer_length + 1; 31 | input_buffer = malloc(sizeof(float complex) * input_buffer_len); 32 | ck_assert(input_buffer != NULL); 33 | doppler_process_rx((float complex *) input_buffer, input_buffer_len, &output, &output_len, dopp); 34 | ck_assert(output == NULL); 35 | } 36 | 37 | END_TEST 38 | 39 | void assert_success_rx(int buffer_length, const char *expected_filename) { 40 | int code = doppler_create(53.72F, 47.57F, 0.0F, 48000, 437525000, 0, 1583840449, buffer_length, tle, &dopp); 41 | ck_assert_int_eq(code, 0); 42 | 43 | input_file = fopen("lucky7.cf32", "rb"); 44 | ck_assert(input_file != NULL); 45 | expected_file = fopen(expected_filename, "rb"); 46 | ck_assert(expected_file != NULL); 47 | 48 | input_buffer = malloc(max_buffer_length * sizeof(float complex)); 49 | ck_assert(input_buffer != NULL); 50 | expected_buffer = malloc(max_buffer_length * sizeof(float complex)); 51 | ck_assert(expected_buffer != NULL); 52 | while (true) { 53 | size_t actually_read = fread(input_buffer, sizeof(float complex), max_buffer_length, input_file); 54 | if (actually_read == 0) { 55 | break; 56 | } 57 | float complex *output = NULL; 58 | size_t output_len = 0; 59 | doppler_process_rx((float complex *) input_buffer, actually_read, &output, &output_len, dopp); 60 | 61 | size_t actually_expected_read = fread(expected_buffer, sizeof(float complex), actually_read, expected_file); 62 | ck_assert_int_eq(actually_read, actually_expected_read); 63 | 64 | assert_complex_array((const float *) expected_buffer, actually_expected_read, output, output_len); 65 | } 66 | } 67 | 68 | START_TEST (test_success_rx) { 69 | assert_success_rx(max_buffer_length, "lucky7.expected.cf32"); 70 | } 71 | 72 | END_TEST 73 | 74 | START_TEST (test_success_rx_47000) { 75 | assert_success_rx(47000, "lucky7.expected.47000.cf32"); 76 | } 77 | 78 | END_TEST 79 | 80 | START_TEST (test_success_rx_95000) { 81 | assert_success_rx(95000, "lucky7.expected.95000.cf32"); 82 | } 83 | 84 | END_TEST 85 | 86 | 87 | START_TEST (test_success_tx) { 88 | int code = doppler_create(53.72F, 47.57F, 0.0F, 48000, 437525000, 0, 1583840449, max_buffer_length, tle, &dopp); 89 | ck_assert_int_eq(code, 0); 90 | 91 | // use RX inverted input data for test 92 | input_file = fopen("lucky7.expected.cf32", "rb"); 93 | ck_assert(input_file != NULL); 94 | expected_file = fopen("lucky7.cf32", "rb"); 95 | ck_assert(expected_file != NULL); 96 | 97 | input_buffer = malloc(max_buffer_length * sizeof(float complex)); 98 | ck_assert(input_buffer != NULL); 99 | expected_buffer = malloc(max_buffer_length * sizeof(float complex)); 100 | ck_assert(expected_buffer != NULL); 101 | while (true) { 102 | size_t actually_read = fread(input_buffer, sizeof(float complex), max_buffer_length, input_file); 103 | if (actually_read == 0) { 104 | break; 105 | } 106 | float complex *output = NULL; 107 | size_t output_len = 0; 108 | doppler_process_tx((float complex *) input_buffer, actually_read, &output, &output_len, dopp); 109 | 110 | size_t actually_expected_read = fread(expected_buffer, sizeof(float complex), actually_read, expected_file); 111 | ck_assert_int_eq(actually_read, actually_expected_read); 112 | 113 | assert_complex_array((const float *) expected_buffer, actually_expected_read, output, output_len); 114 | } 115 | } 116 | 117 | END_TEST 118 | 119 | void teardown() { 120 | if (dopp != NULL) { 121 | doppler_destroy(dopp); 122 | dopp = NULL; 123 | } 124 | if (input_file != NULL) { 125 | fclose(input_file); 126 | input_file = NULL; 127 | } 128 | if (input_buffer != NULL) { 129 | free(input_buffer); 130 | input_buffer = NULL; 131 | } 132 | if (expected_file != NULL) { 133 | fclose(expected_file); 134 | expected_file = NULL; 135 | } 136 | if (expected_buffer != NULL) { 137 | free(expected_buffer); 138 | expected_buffer = NULL; 139 | } 140 | } 141 | 142 | void setup() { 143 | //do nothing 144 | } 145 | 146 | Suite *common_suite(void) { 147 | Suite *s; 148 | TCase *tc_core; 149 | 150 | s = suite_create("doppler"); 151 | 152 | /* Core test case */ 153 | tc_core = tcase_create("Core"); 154 | 155 | tcase_add_test(tc_core, test_success_rx); 156 | tcase_add_test(tc_core, test_success_rx_47000); 157 | tcase_add_test(tc_core, test_success_rx_95000); 158 | tcase_add_test(tc_core, test_success_tx); 159 | tcase_add_test(tc_core, test_invalid_arguments); 160 | 161 | tcase_add_checked_fixture(tc_core, setup, teardown); 162 | suite_add_tcase(s, tc_core); 163 | 164 | return s; 165 | } 166 | 167 | int main(void) { 168 | int number_failed; 169 | Suite *s; 170 | SRunner *sr; 171 | 172 | s = common_suite(); 173 | sr = srunner_create(s); 174 | 175 | srunner_set_fork_status(sr, CK_NOFORK); 176 | srunner_run_all(sr, CK_NORMAL); 177 | number_failed = srunner_ntests_failed(sr); 178 | srunner_free(sr); 179 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 180 | } 181 | -------------------------------------------------------------------------------- /src/sdr/iio_lib.c: -------------------------------------------------------------------------------- 1 | #include "iio_lib.h" 2 | #include 3 | #include 4 | #include 5 | 6 | void *iio_lib_dlsym(void *handle, const char *symbol) { 7 | void *result = dlsym(handle, symbol); 8 | if (result == NULL) { 9 | fprintf(stderr, "unable to load function %s: %s\n", symbol, dlerror()); 10 | } 11 | return result; 12 | } 13 | 14 | int iio_lib_create(iio_lib **lib) { 15 | struct iio_lib_t *result = malloc(sizeof(struct iio_lib_t)); 16 | if (result == NULL) { 17 | return -ENOMEM; 18 | } 19 | *result = (struct iio_lib_t) {0}; 20 | 21 | #if defined(__APPLE__) 22 | void *handle = dlopen("iio", RTLD_LAZY); 23 | #else 24 | void *handle = dlopen("libiio.so", RTLD_LAZY); 25 | #endif 26 | if (!handle) { 27 | fprintf(stderr, "unable to load libiio: %s\n", dlerror()); 28 | iio_lib_destroy(result); 29 | return -1; 30 | } 31 | result->handle = handle; 32 | result->iio_buffer_first = iio_lib_dlsym(result->handle, "iio_buffer_first"); 33 | if (result->iio_buffer_first == NULL) { 34 | iio_lib_destroy(result); 35 | return -1; 36 | } 37 | result->iio_buffer_end = iio_lib_dlsym(result->handle, "iio_buffer_end"); 38 | if (result->iio_buffer_end == NULL) { 39 | iio_lib_destroy(result); 40 | return -1; 41 | } 42 | result->iio_buffer_push_partial = iio_lib_dlsym(result->handle, "iio_buffer_push_partial"); 43 | if (result->iio_buffer_push_partial == NULL) { 44 | iio_lib_destroy(result); 45 | return -1; 46 | } 47 | result->iio_buffer_refill = iio_lib_dlsym(result->handle, "iio_buffer_refill"); 48 | if (result->iio_buffer_refill == NULL) { 49 | iio_lib_destroy(result); 50 | return -1; 51 | } 52 | result->iio_context_find_device = iio_lib_dlsym(result->handle, "iio_context_find_device"); 53 | if (result->iio_context_find_device == NULL) { 54 | iio_lib_destroy(result); 55 | return -1; 56 | } 57 | result->iio_device_find_channel = iio_lib_dlsym(result->handle, "iio_device_find_channel"); 58 | if (result->iio_device_find_channel == NULL) { 59 | iio_lib_destroy(result); 60 | return -1; 61 | } 62 | result->iio_strerror = iio_lib_dlsym(result->handle, "iio_strerror"); 63 | if (result->iio_strerror == NULL) { 64 | iio_lib_destroy(result); 65 | return -1; 66 | } 67 | result->iio_channel_attr_write = iio_lib_dlsym(result->handle, "iio_channel_attr_write"); 68 | if (result->iio_channel_attr_write == NULL) { 69 | iio_lib_destroy(result); 70 | return -1; 71 | } 72 | result->iio_channel_attr_write_longlong = iio_lib_dlsym(result->handle, "iio_channel_attr_write_longlong"); 73 | if (result->iio_channel_attr_write_longlong == NULL) { 74 | iio_lib_destroy(result); 75 | return -1; 76 | } 77 | result->iio_device_attr_write_bool = iio_lib_dlsym(result->handle, "iio_device_attr_write_bool"); 78 | if (result->iio_device_attr_write_bool == NULL) { 79 | iio_lib_destroy(result); 80 | return -1; 81 | } 82 | result->iio_channel_attr_write_bool = iio_lib_dlsym(result->handle, "iio_channel_attr_write_bool"); 83 | if (result->iio_channel_attr_write_bool == NULL) { 84 | iio_lib_destroy(result); 85 | return -1; 86 | } 87 | result->iio_channel_attr_write_double = iio_lib_dlsym(result->handle, "iio_channel_attr_write_double"); 88 | if (result->iio_channel_attr_write_double == NULL) { 89 | iio_lib_destroy(result); 90 | return -1; 91 | } 92 | result->iio_device_attr_write_raw = iio_lib_dlsym(result->handle, "iio_device_attr_write_raw"); 93 | if (result->iio_device_attr_write_raw == NULL) { 94 | iio_lib_destroy(result); 95 | return -1; 96 | } 97 | result->iio_create_scan_context = iio_lib_dlsym(result->handle, "iio_create_scan_context"); 98 | if (result->iio_create_scan_context == NULL) { 99 | iio_lib_destroy(result); 100 | return -1; 101 | } 102 | result->iio_scan_context_get_info_list = iio_lib_dlsym(result->handle, "iio_scan_context_get_info_list"); 103 | if (result->iio_scan_context_get_info_list == NULL) { 104 | iio_lib_destroy(result); 105 | return -1; 106 | } 107 | result->iio_scan_context_destroy = iio_lib_dlsym(result->handle, "iio_scan_context_destroy"); 108 | if (result->iio_scan_context_destroy == NULL) { 109 | iio_lib_destroy(result); 110 | return -1; 111 | } 112 | result->iio_context_info_get_uri = iio_lib_dlsym(result->handle, "iio_context_info_get_uri"); 113 | if (result->iio_context_info_get_uri == NULL) { 114 | iio_lib_destroy(result); 115 | return -1; 116 | } 117 | result->iio_create_context_from_uri = iio_lib_dlsym(result->handle, "iio_create_context_from_uri"); 118 | if (result->iio_create_context_from_uri == NULL) { 119 | iio_lib_destroy(result); 120 | return -1; 121 | } 122 | result->iio_context_info_list_free = iio_lib_dlsym(result->handle, "iio_context_info_list_free"); 123 | if (result->iio_context_info_list_free == NULL) { 124 | iio_lib_destroy(result); 125 | return -1; 126 | } 127 | result->iio_context_set_timeout = iio_lib_dlsym(result->handle, "iio_context_set_timeout"); 128 | if (result->iio_context_set_timeout == NULL) { 129 | iio_lib_destroy(result); 130 | return -1; 131 | } 132 | result->iio_channel_enable = iio_lib_dlsym(result->handle, "iio_channel_enable"); 133 | if (result->iio_channel_enable == NULL) { 134 | iio_lib_destroy(result); 135 | return -1; 136 | } 137 | result->iio_device_create_buffer = iio_lib_dlsym(result->handle, "iio_device_create_buffer"); 138 | if (result->iio_device_create_buffer == NULL) { 139 | iio_lib_destroy(result); 140 | return -1; 141 | } 142 | result->iio_buffer_destroy = iio_lib_dlsym(result->handle, "iio_buffer_destroy"); 143 | if (result->iio_buffer_destroy == NULL) { 144 | iio_lib_destroy(result); 145 | return -1; 146 | } 147 | result->iio_channel_disable = iio_lib_dlsym(result->handle, "iio_channel_disable"); 148 | if (result->iio_channel_disable == NULL) { 149 | iio_lib_destroy(result); 150 | return -1; 151 | } 152 | result->iio_context_destroy = iio_lib_dlsym(result->handle, "iio_context_destroy"); 153 | if (result->iio_context_destroy == NULL) { 154 | iio_lib_destroy(result); 155 | return -1; 156 | } 157 | 158 | *lib = result; 159 | return 0; 160 | } 161 | 162 | void iio_lib_destroy(iio_lib *lib) { 163 | if (lib == NULL) { 164 | return; 165 | } 166 | if (lib->handle != NULL) { 167 | dlclose(lib->handle); 168 | } 169 | free(lib); 170 | } 171 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(sdr-modem) 3 | 4 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") 6 | if (CMAKE_BUILD_TYPE MATCHES Debug) 7 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") 8 | endif () 9 | 10 | add_library(sdr_modemLib 11 | ${CMAKE_CURRENT_SOURCE_DIR}/src/api_utils.c 12 | ${CMAKE_CURRENT_SOURCE_DIR}/src/api.pb-c.c 13 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp_worker.c 14 | ${CMAKE_CURRENT_SOURCE_DIR}/src/linked_list.c 15 | ${CMAKE_CURRENT_SOURCE_DIR}/src/queue.c 16 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr_worker.c 17 | ${CMAKE_CURRENT_SOURCE_DIR}/src/server_config.c 18 | ${CMAKE_CURRENT_SOURCE_DIR}/src/tcp_server.c 19 | ${CMAKE_CURRENT_SOURCE_DIR}/src/tcp_utils.c 20 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/clock_recovery_mm.c 21 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/dc_blocker.c 22 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/doppler.c 23 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/fir_filter.c 24 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/frequency_modulator.c 25 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/fsk_demod.c 26 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/gaussian_taps.c 27 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/gfsk_mod.c 28 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/interp_fir_filter.c 29 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/lpf.c 30 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/lpf_taps.c 31 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/mmse_fir_interpolator.c 32 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/quadrature_demod.c 33 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp/sig_source.c 34 | ${CMAKE_CURRENT_SOURCE_DIR}/src/math/fast_atan2f.c 35 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/plutosdr.c 36 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/file_source.c 37 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/iio_lib.c 38 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/sdr_server_client.c 39 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sgpsdp/sgp4sdp4.c 40 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sgpsdp/sgp_in.c 41 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sgpsdp/sgp_math.c 42 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sgpsdp/sgp_obs.c 43 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sgpsdp/sgp_time.c 44 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sgpsdp/solar.c 45 | ) 46 | 47 | find_package(PkgConfig REQUIRED) 48 | 49 | pkg_check_modules(PC_VOLK REQUIRED volk) 50 | include_directories(${PC_VOLK_INCLUDE_DIRS}) 51 | link_directories(${PC_VOLK_LIBRARY_DIRS}) 52 | target_link_libraries(sdr_modemLib ${PC_VOLK_LIBRARIES}) 53 | 54 | pkg_check_modules(PC_LIBCONFIG REQUIRED libconfig) 55 | include_directories(${PC_LIBCONFIG_INCLUDE_DIRS}) 56 | link_directories(${PC_LIBCONFIG_LIBRARY_DIRS}) 57 | target_link_libraries(sdr_modemLib ${PC_LIBCONFIG_LIBRARIES}) 58 | 59 | pkg_check_modules(PC_LIBPROTOBUFC REQUIRED libprotobuf-c) 60 | include_directories(${PC_LIBPROTOBUFC_INCLUDE_DIRS}) 61 | link_directories(${PC_LIBPROTOBUFC_LIBRARY_DIRS}) 62 | target_link_libraries(sdr_modemLib ${PC_LIBPROTOBUFC_LIBRARIES}) 63 | 64 | if (APPLE) 65 | find_path(LIBIIO_INCLUDE_DIRS iio.h) 66 | include_directories(${LIBIIO_INCLUDE_DIRS}) 67 | link_directories("/Library/Frameworks/iio.framework") 68 | else () 69 | pkg_check_modules(PC_LIBIIO REQUIRED libiio) 70 | include_directories(${PC_LIBIIO_INCLUDE_DIRS}) 71 | endif () 72 | 73 | find_package(Threads REQUIRED) 74 | target_link_libraries(sdr_modemLib ${CMAKE_THREAD_LIBS_INIT}) 75 | target_link_libraries(sdr_modemLib m ${CMAKE_DL_LIBS}) 76 | 77 | add_executable(sdr_modem ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c) 78 | target_link_libraries(sdr_modem sdr_modemLib) 79 | 80 | install(TARGETS sdr_modem DESTINATION /usr/bin/) 81 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/config.conf DESTINATION /etc/sdr-modem/) 82 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/sdr-modem.service DESTINATION /lib/systemd/system/) 83 | 84 | enable_testing() 85 | 86 | file(GLOB TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test/test_*.c) 87 | file(GLOB TEST_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test/resources/*) 88 | file(COPY ${TEST_RESOURCES} DESTINATION "${CMAKE_BINARY_DIR}") 89 | file(GLOB PERF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test/perf_*.c) 90 | 91 | file(GLOB AUX_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/api.pb-c.c) 92 | list(FILTER AUX_TEST_SOURCES EXCLUDE REGEX "test_.*\\.c") 93 | list(FILTER AUX_TEST_SOURCES EXCLUDE REGEX "perf_.*\\.c") 94 | list(FILTER AUX_TEST_SOURCES EXCLUDE REGEX "mock_.*\\.c") 95 | 96 | add_library(sdr_modemTestLib ${AUX_TEST_SOURCES}) 97 | 98 | pkg_check_modules(PC_CHECK REQUIRED check) 99 | include_directories(${PC_CHECK_INCLUDE_DIRS}) 100 | link_directories(${PC_CHECK_LIBRARY_DIRS}) 101 | 102 | foreach (curTest ${TEST_SOURCES}) 103 | get_filename_component(curTestName ${curTest} NAME_WE) 104 | add_test(NAME ${curTestName} COMMAND ${curTestName} ${curTest}) 105 | add_executable(${curTestName} ${curTest}) 106 | target_link_libraries(${curTestName} sdr_modemLib sdr_modemTestLib ${PC_CHECK_LIBRARIES}) 107 | endforeach () 108 | 109 | foreach (curPerfTest ${PERF_SOURCES}) 110 | get_filename_component(curPerfTestName ${curPerfTest} NAME_WE) 111 | add_executable(${curPerfTestName} ${curPerfTest}) 112 | target_link_libraries(${curPerfTestName} sdr_modemLib sdr_modemTestLib ${PC_CHECK_LIBRARIES}) 113 | endforeach () 114 | 115 | if (CMAKE_BUILD_TYPE MATCHES Debug) 116 | add_custom_target("coverage") 117 | add_custom_command(TARGET "coverage" COMMAND gcov ${CMAKE_BINARY_DIR}/CMakeFiles/sdr_modemLib.dir/src/*.c.o ${CMAKE_BINARY_DIR}/CMakeFiles/sdr_modemLib.dir/src/dsp/*.c.o ${CMAKE_BINARY_DIR}/CMakeFiles/sdr_modemLib.dir/src/math/*.c.o ${CMAKE_BINARY_DIR}/CMakeFiles/sdr_modemLib.dir/src/sdr/*.c.o ${CMAKE_BINARY_DIR}/CMakeFiles/sdr_modemLib.dir/src/sgpsdp/*.c.o) 118 | endif () 119 | 120 | set(CPACK_GENERATOR "DEB") 121 | set(CPACK_DEBIAN_PACKAGE_NAME "sdr-modem") 122 | set(OS_CODENAME "$ENV{VERSION_CODENAME}") 123 | if (OS_CODENAME STREQUAL "trixie" OR 124 | OS_CODENAME STREQUAL "noble") 125 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libvolk-bin, libprotobuf-c1, libconfig11, zlib1g") 126 | else () 127 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libvolk2-bin, libprotobuf-c1, libconfig9, zlib1g") 128 | endif() 129 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Andrey Rodionov ") 130 | set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Modem based on software defined radios") 131 | set(CPACK_DEBIAN_PACKAGE_SECTION "comm") 132 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dernasherbrezon/sdr-modem") 133 | set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/debian/prerm;${CMAKE_CURRENT_SOURCE_DIR}/debian/postrm") 134 | set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON) 135 | set(CPACK_DEBIAN_FILE_NAME "sdr-modem_${CPACK_DEBIAN_PACKAGE_VERSION}_${CUSTOM_ARCHITECTURE}.deb") 136 | include(CPack) -------------------------------------------------------------------------------- /test/test_frequency_modulator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../src/dsp/frequency_modulator.h" 6 | #include "utils.h" 7 | 8 | frequency_modulator *mod = NULL; 9 | float *float_input = NULL; 10 | 11 | START_TEST (test_input_exceeded) { 12 | int code = frequency_modulator_create(1.2F, 100, &mod); 13 | ck_assert_int_eq(code, 0); 14 | 15 | setup_input_data(&float_input, 0, 200); 16 | 17 | float complex *output = NULL; 18 | size_t output_len = 0; 19 | frequency_modulator_process(float_input, 200, &output, &output_len, mod); 20 | ck_assert_int_eq(code, 0); 21 | } 22 | END_TEST 23 | 24 | START_TEST (test_normal) { 25 | int code = frequency_modulator_create(1.2F, 1000, &mod); 26 | ck_assert_int_eq(code, 0); 27 | 28 | setup_input_data(&float_input, 0, 200); 29 | 30 | float complex *output = NULL; 31 | size_t output_len = 0; 32 | frequency_modulator_process(float_input, 100, &output, &output_len, mod); 33 | 34 | const float expected[] = {1.000000F, 0.000000F, 0.362358F, 0.932039F, -0.896758F, -0.442521F, 0.608351F, 0.793668F, 0.843854F, -0.536573F, 0.660317F, -0.750987F, 0.997739F, 0.067209F, -0.575552F, 0.817765F, 0.709299F, -0.704907F, -0.829308F, -0.558792F, -0.999647F, -0.026551F, -0.789881F, 35 | -0.613259F, 0.797425F, -0.603419F, -0.727760F, 0.685832F, 0.943984F, 0.329991F, 0.871147F, -0.491022F, 0.986774F, -0.162103F, 0.182142F, 0.983272F, -0.543253F, -0.839569F, -0.232403F, 0.972620F, 0.782203F, 0.623024F, 0.738564F, 0.674184F, -0.422603F, 0.906315F, 36 | -0.235772F, -0.971808F, -0.283691F, 0.958916F, 0.903679F, 0.428210F, 0.974437F, 0.224662F, 0.352398F, 0.935850F, -0.968128F, -0.250456F, 0.879674F, 0.475577F, 0.359101F, -0.933299F, -0.131414F, -0.991328F, 0.538888F, -0.842377F, 0.622249F, 0.782820F, -0.653598F, 37 | -0.756842F, -0.432232F, 0.901762F, 0.329070F, 0.944306F, -0.082316F, 0.996606F, -0.991654F, -0.128931F, 0.981123F, -0.193385F, -0.776629F, -0.629958F, -0.927162F, 0.374662F, -0.969038F, 0.246910F, -0.466910F, -0.884305F, 0.887927F, 0.459984F, -0.479333F, -0.877633F, 38 | -0.961657F, 0.274255F, -0.910437F, 0.413649F, -0.811051F, -0.584975F, 0.964998F, -0.262258F, -0.999015F, -0.044363F, 0.016727F, 0.999860F, 0.433665F, 0.901074F, -0.314155F, 0.949372F, -0.753803F, -0.657100F, 0.736091F, 0.676883F, 0.388708F, -0.921361F, -0.310563F, 39 | -0.950553F, 0.168347F, -0.985728F, 0.960120F, 0.279589F, -0.999643F, -0.026732F, 0.565254F, 0.824917F, 0.999601F, -0.028262F, 0.984968F, 0.172737F, -0.004637F, 0.999989F, -0.509996F, -0.860177F, -0.124630F, 0.992203F, 0.915421F, 0.402499F, 0.945088F, 0.326817F, 40 | 0.118941F, 0.992901F, -0.808868F, -0.587990F, 0.535648F, 0.844441F, 0.847695F, -0.530483F, 0.602898F, -0.797818F, 0.996112F, -0.088098F, -0.367572F, 0.929995F, 0.453030F, -0.891495F, -0.981418F, -0.191882F, -0.898436F, 0.439105F, -0.995234F, -0.097512F, 0.275482F, 41 | -0.961306F, -0.089550F, 0.995982F, 0.891298F, -0.453418F, 0.168284F, -0.985739F, 0.424064F, -0.905632F, 0.944026F, 0.329871F, -0.990740F, 0.135774F, 0.830300F, 0.557316F, 0.812188F, -0.583395F, 0.803736F, -0.594986F, 0.853255F, 0.521494F, -0.978663F, 0.205472F, 42 | 0.971997F, 0.234995F, 0.305794F, -0.952098F, 0.013241F, -0.999912F, 0.793114F, -0.609073F, 0.122519F, 0.992466F, 0.038138F, -0.999273F, -0.985333F, 0.170641F, -0.730190F, 0.683244F, -0.991206F, 0.132328F, 0.115900F, -0.993261F, 0.006050F, 0.999982F, 0.878127F, 43 | -0.478427F, 0.206535F, -0.978439F, 0.517842F, -0.855476F, 0.872735F, 0.488194F, -0.994532F, -0.104432F, 0.622063F, 0.782967F, 0.969487F, -0.245141F, 0.981134F, -0.193331F, 0.489964F, 0.871742F, -0.932267F, -0.361770F, 0.635779F, 0.771871F, 0.853311F, -0.521402F, 44 | 0.711919F, -0.702262F, 0.981711F, 0.190380F, -0.710760F, 0.703434F, 0.851332F, -0.524628F, -0.640339F, -0.768093F, -0.934979F, -0.354702F, -0.497980F, -0.867188F, 0.979074F, -0.203504F, -0.966343F, 0.257256F, 0.633468F, 0.773769F, 0.996102F, 0.088207F, 0.881357F, 45 | 0.472451F, -0.501400F, 0.865215F, 0.185701F, -0.982606F, -0.866701F, 0.498828F, 0.031077F, 0.999517F, -0.089361F, 0.995999F, -0.987138F, 0.159869F, 0.709383F, -0.704823F, -0.979346F, 0.202191F, -0.004384F, 0.999990F, 0.157579F, 0.987506F, -0.770314F, 0.637665F, 46 | -0.025465F, -0.999676F, -0.266660F, 0.963791F, 0.981102F, 0.193491F, 0.968643F, -0.248459F, 0.875996F, 0.482318F, -0.774617F, 0.632431F, 0.782280F, -0.622927F, -0.857747F, -0.514072F, -0.982180F, 0.187944F, -0.960464F, -0.278406F, 0.372553F, -0.928011F, -0.110555F, 47 | 0.993870F, 0.862404F, -0.506220F, 0.027818F, -0.999613F, 0.214167F, -0.976797F, 0.999479F, 0.032272F, -0.866881F, 0.498514F, 0.992303F, 0.123830F, 0.389033F, -0.921224F, 0.299733F, -0.954023F, 0.986853F, -0.161621F, -0.540571F, 0.841298F, 0.803984F, -0.594652F, 48 | -0.600867F, -0.799349F, -0.853643F, -0.520859F, -0.200702F, -0.979652F, 0.969359F, 0.245650F, -0.946421F, -0.322935F, -0.036906F, 0.999319F, 0.584793F, 0.811183F, 0.086836F, 0.996223F, -0.996712F, -0.081021F, 0.992517F, -0.122107F, -0.642313F, -0.766442F, -0.997449F, 49 | 0.071380F, -0.983292F, -0.182033F, 0.067871F, -0.997694F, 0.406651F, 0.913584F, 0.289521F, -0.957172F, -0.804114F, -0.594475F, -0.820837F, -0.571163F, 0.208075F, -0.978113F, 0.531429F, 0.847103F, -0.130587F, -0.991437F, -0.997462F, 0.071200F, -0.927497F, 0.373831F, 50 | -0.874613F, -0.484821F, 0.854131F, -0.520058F, -0.920757F, 0.390136F, 0.584500F, 0.811394F, 0.938867F, 0.344280F, 0.575499F, 0.817802F, -0.929153F, 0.369696F, 0.869934F, -0.493167F, -0.853360F, -0.521322F, -0.946092F, 0.323898F, -0.999976F, 0.006922F, -0.055546F, 51 | -0.998456F, 0.458054F, 0.888924F, 0.300423F, -0.953806F, -0.755421F, -0.655240F, -0.728792F, -0.684735F}; 52 | assert_complex_array(expected, 100, output, output_len); 53 | } 54 | 55 | END_TEST 56 | 57 | void teardown() { 58 | if (mod != NULL) { 59 | frequency_modulator_destroy(mod); 60 | mod = NULL; 61 | } 62 | if (float_input != NULL) { 63 | free(float_input); 64 | float_input = NULL; 65 | } 66 | } 67 | 68 | void setup() { 69 | //do nothing 70 | } 71 | 72 | Suite *common_suite(void) { 73 | Suite *s; 74 | TCase *tc_core; 75 | 76 | s = suite_create("frequency_modulator"); 77 | 78 | /* Core test case */ 79 | tc_core = tcase_create("Core"); 80 | 81 | tcase_add_test(tc_core, test_normal); 82 | tcase_add_test(tc_core, test_input_exceeded); 83 | 84 | tcase_add_checked_fixture(tc_core, setup, teardown); 85 | suite_add_tcase(s, tc_core); 86 | 87 | return s; 88 | } 89 | 90 | int main(void) { 91 | int number_failed; 92 | Suite *s; 93 | SRunner *sr; 94 | 95 | s = common_suite(); 96 | sr = srunner_create(s); 97 | 98 | srunner_set_fork_status(sr, CK_NOFORK); 99 | srunner_run_all(sr, CK_NORMAL); 100 | number_failed = srunner_ntests_failed(sr); 101 | srunner_free(sr); 102 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 103 | } 104 | -------------------------------------------------------------------------------- /test/sdr_server_mock.c: -------------------------------------------------------------------------------- 1 | #include "sdr_server_mock.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "../src/sdr/sdr_server_api.h" 16 | #include "../src/tcp_utils.h" 17 | 18 | struct sdr_server_mock_t { 19 | int server_socket; 20 | volatile sig_atomic_t is_running; 21 | pthread_t acceptor_thread; 22 | 23 | void (*handler)(int client_socket, sdr_server_mock *server); 24 | 25 | float complex *input; 26 | size_t input_len; 27 | 28 | pthread_mutex_t mutex; 29 | pthread_cond_t condition; 30 | int client_socket; 31 | }; 32 | 33 | void mock_response_success(int client_socket, sdr_server_mock *server) { 34 | struct sdr_server_message_header header; 35 | int code = -2; 36 | while (code < -1) { 37 | code = tcp_utils_read_data(&header, sizeof(struct sdr_server_message_header), client_socket); 38 | } 39 | if (code != 0) { 40 | fprintf(stderr, "unable to read header from sdr-modem\n"); 41 | return; 42 | } 43 | if (header.protocol_version != SDR_SERVER_PROTOCOL_VERSION) { 44 | fprintf(stderr, "invalid protocol version: %d\n", header.protocol_version); 45 | return; 46 | } 47 | if (header.type != SDR_SERVER_TYPE_REQUEST) { 48 | fprintf(stderr, "expected type = request. got: %d\n", header.type); 49 | return; 50 | } 51 | struct sdr_server_request request; 52 | code = tcp_utils_read_data(&request, sizeof(struct sdr_server_request), client_socket); 53 | if (code != 0) { 54 | fprintf(stderr, "unable to read request from sdr-modem\n"); 55 | return; 56 | } 57 | 58 | header.type = SDR_SERVER_TYPE_RESPONSE; 59 | header.protocol_version = SDR_SERVER_PROTOCOL_VERSION; 60 | 61 | struct sdr_server_response response; 62 | response.status = SDR_SERVER_RESPONSE_STATUS_SUCCESS; 63 | response.details = 0; 64 | 65 | // it is possible to directly populate *buffer with the fields, 66 | // however populating structs and then serializing them into byte array 67 | // is more readable 68 | size_t total_len = sizeof(struct sdr_server_message_header) + sizeof(struct sdr_server_response); 69 | uint8_t *buffer = malloc(total_len); 70 | if (buffer == NULL) { 71 | return; 72 | } 73 | memcpy(buffer, &header, sizeof(struct sdr_server_message_header)); 74 | memcpy(buffer + sizeof(struct sdr_server_message_header), &response, sizeof(struct sdr_server_response)); 75 | tcp_utils_write_data(buffer, total_len, client_socket); 76 | free(buffer); 77 | } 78 | 79 | static void *acceptor_worker(void *arg) { 80 | sdr_server_mock *server = (sdr_server_mock *) arg; 81 | struct sockaddr_in address; 82 | while (server->is_running) { 83 | int client_socket; 84 | int addrlen = sizeof(address); 85 | if ((client_socket = accept(server->server_socket, (struct sockaddr *) &address, (socklen_t *) &addrlen)) < 0) { 86 | break; 87 | } 88 | 89 | struct timeval tv; 90 | tv.tv_sec = 5000; 91 | tv.tv_usec = 0; 92 | if (setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv)) { 93 | close(client_socket); 94 | perror("setsockopt - SO_RCVTIMEO"); 95 | continue; 96 | } 97 | 98 | //doesn't support multiple clients actually 99 | //no needed for tests 100 | pthread_mutex_lock(&server->mutex); 101 | if (server->handler != NULL) { 102 | server->handler(client_socket, server); 103 | } 104 | server->client_socket = client_socket; 105 | pthread_cond_broadcast(&server->condition); 106 | pthread_mutex_unlock(&server->mutex); 107 | } 108 | 109 | printf("sdr server mock stopped\n"); 110 | if (server->client_socket >= 0) { 111 | close(server->client_socket); 112 | } 113 | return (void *) 0; 114 | } 115 | 116 | int sdr_server_mock_send(float complex *input, size_t input_len, sdr_server_mock *server) { 117 | pthread_mutex_lock(&server->mutex); 118 | while (server->client_socket < 0) { 119 | pthread_cond_wait(&server->condition, &server->mutex); 120 | } 121 | pthread_mutex_unlock(&server->mutex); 122 | return tcp_utils_write_data((uint8_t *) input, sizeof(float complex) * input_len, server->client_socket); 123 | } 124 | 125 | int sdr_server_mock_create(const char *addr, int port, void (*handler)(int client_socket, sdr_server_mock *server), uint32_t max_output_buffer_length, sdr_server_mock **server) { 126 | struct sdr_server_mock_t *result = malloc(sizeof(struct sdr_server_mock_t)); 127 | if (result == NULL) { 128 | return -ENOMEM; 129 | } 130 | *result = (struct sdr_server_mock_t) {0}; 131 | 132 | int server_socket = socket(AF_INET, SOCK_STREAM, 0); 133 | if (server_socket == 0) { 134 | free(result); 135 | perror("socket creation failed"); 136 | return -1; 137 | } 138 | result->server_socket = server_socket; 139 | result->is_running = true; 140 | result->handler = handler; 141 | result->input_len = max_output_buffer_length; 142 | result->input = malloc(sizeof(float complex) * result->input_len); 143 | if (result->input == NULL) { 144 | return -ENOMEM; 145 | } 146 | result->condition = (pthread_cond_t) PTHREAD_COND_INITIALIZER; 147 | result->mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; 148 | result->client_socket = -1; 149 | 150 | int opt = 1; 151 | if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { 152 | free(result); 153 | perror("setsockopt - SO_REUSEADDR"); 154 | return -1; 155 | } 156 | 157 | #ifdef SO_REUSEPORT 158 | if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))) { 159 | free(result); 160 | perror("setsockopt - SO_REUSEPORT"); 161 | return -1; 162 | } 163 | #endif 164 | 165 | struct sockaddr_in address; 166 | address.sin_family = AF_INET; 167 | if (inet_pton(AF_INET, addr, &address.sin_addr) <= 0) { 168 | free(result); 169 | perror("invalid address"); 170 | return -1; 171 | } 172 | address.sin_port = htons(port); 173 | 174 | if (bind(server_socket, (struct sockaddr *) &address, sizeof(address)) < 0) { 175 | free(result); 176 | perror("bind failed"); 177 | return -1; 178 | } 179 | if (listen(server_socket, 3) < 0) { 180 | free(result); 181 | perror("listen failed"); 182 | return -1; 183 | } 184 | 185 | pthread_t acceptor_thread; 186 | int code = pthread_create(&acceptor_thread, NULL, &acceptor_worker, result); 187 | if (code != 0) { 188 | free(result); 189 | return -1; 190 | } 191 | result->acceptor_thread = acceptor_thread; 192 | 193 | *server = result; 194 | return 0; 195 | } 196 | 197 | void sdr_server_mock_destroy(sdr_server_mock *server) { 198 | if (server == NULL) { 199 | return; 200 | } 201 | server->is_running = false; 202 | // close is not enough to exit from the blocking "accept" method 203 | // execute shutdown first 204 | int code = shutdown(server->server_socket, SHUT_RDWR); 205 | if (code != 0) { 206 | close(server->server_socket); 207 | } 208 | pthread_join(server->acceptor_thread, NULL); 209 | 210 | if (server->input != NULL) { 211 | free(server->input); 212 | } 213 | free(server); 214 | } --------------------------------------------------------------------------------