diff --git a/CMakeLists.txt b/CMakeLists.txt index 23a13f1..afd19f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ add_executable(blah2 src/capture/rspduo/RspDuo.cpp src/capture/usrp/Usrp.cpp src/capture/iqsimulator/IqSimulator.cpp + src/capture/iqsimulator/TgtGen.cpp src/process/ambiguity/Ambiguity.cpp src/process/clutter/WienerHopf.cpp src/process/detection/CfarDetector1D.cpp @@ -58,6 +59,7 @@ add_executable(blah2 src/data/Detection.cpp src/data/Track.cpp src/data/meta/Timing.cpp + src/utilities/Conversions.cpp ) target_link_libraries(blah2 PRIVATE diff --git a/config/false_targets.yml b/config/false_targets.yml new file mode 100644 index 0000000..50b8adf --- /dev/null +++ b/config/false_targets.yml @@ -0,0 +1,18 @@ +targets: + - id: 1 + type: "static" + location: + range: 10000 # meters + velocity: + doppler: 50 # Hertz + rcs: -20 # dBsm - this is a bit contrived for a static target + state: "active" + + - id: 2 + type: "static" + location: + range: 30000 # meters + velocity: + doppler: -150 # Hertz + rcs: -20 # dBsm + state: "active" \ No newline at end of file diff --git a/src/capture/Capture.cpp b/src/capture/Capture.cpp index aae2a38..80eb522 100644 --- a/src/capture/Capture.cpp +++ b/src/capture/Capture.cpp @@ -63,11 +63,11 @@ void Capture::process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config) std::unique_ptr Capture::factory_source(const std::string &type, c4::yml::NodeRef config) { - if (type == VALID_TYPE[0]) + if (type == VALID_TYPE[0]) // RspDuo { return std::make_unique(type, fc, fs, path, &saveIq); } - else if (type == VALID_TYPE[1]) + else if (type == VALID_TYPE[1]) // Usrp { std::string address, subdev; std::vector antenna; @@ -88,11 +88,13 @@ std::unique_ptr Capture::factory_source(const std::string &type, c4::yml return std::make_unique(type, fc, fs, path, &saveIq, address, subdev, antenna, gain); } - else if (type == VALID_TYPE[2]) + else if (type == VALID_TYPE[2]) // IqSimulator { - uint32_t n, n_min; + uint32_t n_min; n_min = 2000000; - return std::make_unique(type, fc, fs, path, &saveIq, n_min); + std::string false_targets_config_file_path = "config/false_targets.yml"; + return std::make_unique(type, fc, fs, path, &saveIq, n_min, + false_targets_config_file_path); } // Handle unknown type std::cerr << "Error: Source type does not exist." << std::endl; diff --git a/src/capture/iqsimulator/IqSimulator.cpp b/src/capture/iqsimulator/IqSimulator.cpp index 7d510c3..a117f2b 100644 --- a/src/capture/iqsimulator/IqSimulator.cpp +++ b/src/capture/iqsimulator/IqSimulator.cpp @@ -1,17 +1,15 @@ #include "IqSimulator.h" -#include -#include -#include -#include -#include - // constructor IqSimulator::IqSimulator(std::string _type, uint32_t _fc, uint32_t _fs, - std::string _path, bool *_saveIq, uint32_t _n_min = 1000) + std::string _path, bool *_saveIq, + uint32_t _n_min = 1000, + std::string _falseTargetsConfigFilePath = "config/false_targets.yml") : Source(_type, _fc, _fs, _path, _saveIq) { n_min = _n_min; + u_int64_t total_samples = 0; + false_targets_config_file_path = _falseTargetsConfigFilePath; } void IqSimulator::start() @@ -24,10 +22,15 @@ void IqSimulator::stop() void IqSimulator::process(IqData *buffer1, IqData *buffer2) { + const u_int32_t samples_per_iteration = 1000; + + TgtGen false_targets = TgtGen(false_targets_config_file_path, fs); while (true) { - if (buffer1->get_length() < n_min) + uint32_t n_start = buffer1->get_length(); + if (n_start < n_min) { + // create a random number generator std::random_device rd; std::mt19937 gen(rd()); @@ -35,11 +38,22 @@ void IqSimulator::process(IqData *buffer1, IqData *buffer2) buffer1->lock(); buffer2->lock(); - for (int i = 0; i < 1000; i++) + for (uint16_t i = 0; i < samples_per_iteration; i++) { + buffer1->push_back({(double)dis(gen), (double)dis(gen)}); - buffer2->push_back({(double)dis(gen), (double)dis(gen)}); + try + { + std::complex response = false_targets.process(buffer1); + response += std::complex((double)dis(gen), (double)dis(gen)); + buffer2->push_back(response); + } + catch (const std::exception &e) + { + buffer2->push_back({(double)dis(gen), (double)dis(gen)}); + } } + total_samples += samples_per_iteration; buffer1->unlock(); buffer2->unlock(); } diff --git a/src/capture/iqsimulator/IqSimulator.h b/src/capture/iqsimulator/IqSimulator.h index 250e6d6..a9297c0 100644 --- a/src/capture/iqsimulator/IqSimulator.h +++ b/src/capture/iqsimulator/IqSimulator.h @@ -1,7 +1,9 @@ -/// @file IQSimulator.h -/// @class IQSimulator +/// @file IqSimulator.h +/// @class IqSimulator /// @brief A class to generate simulated IQ data with false targets -/// @details +/// @details This class generates simulated IQ data with false targets. +/// It generates a random reference and surveillance signal and uses the +/// TgtGen class to add false targets to the surveillance signal. /// /// @author bennysomers /// @todo Simulate a single false target @@ -13,18 +15,32 @@ #define IQSIMULATOR_H #include "capture/Source.h" +#include "TgtGen.h" #include "data/IqData.h" #include #include +#include +#include +#include +#include class IqSimulator : public Source { private: /// @brief Number of samples to generate each loop. - /// @details This is the number of samples to generate each time the process method is called. It is also the threshold for the minimum number of samples left in the buffer before new samples will be generated. + /// @details This is the threshold for the minimum number of samples + /// left in the buffer before new samples will be generated. uint32_t n_min; + /// @brief Total number of samples generated. + /// @details This is used to keep track of the total number of samples + /// generated, so that the Doppler shift can be calculated. + u_int64_t total_samples; + + /// @brief Path to the false targets configuration file. + std::string false_targets_config_file_path; + public: /// @brief Constructor. /// @param type Type of source. = "IQSimulator" @@ -35,7 +51,7 @@ public: /// @param n Number of samples. /// @return The object. IqSimulator(std::string type, uint32_t fc, uint32_t fs, std::string path, - bool *saveIq, uint32_t n_min); + bool *saveIq, uint32_t n_min, std::string false_targets_config_file_path); /// @brief Implement capture function on IQSimulator. /// @param buffer1 Pointer to reference buffer. diff --git a/src/capture/iqsimulator/TgtGen.cpp b/src/capture/iqsimulator/TgtGen.cpp new file mode 100644 index 0000000..e74ce30 --- /dev/null +++ b/src/capture/iqsimulator/TgtGen.cpp @@ -0,0 +1,125 @@ +#include "TgtGen.h" + +// this is straight up copied from blah2.cpp, but I don't know the best way to access that function here. +// edit: put it in utilities? +std::string ryml_get_file_copy(const char *filename); + +// constants +const std::string TgtGen::VALID_TYPE[1] = {"static"}; +const std::string TgtGen::VALID_STATE[1] = {"active"}; + +// constructor +TgtGen::TgtGen(std::string configPath, uint32_t fs) +{ + // Read in the config file + std::string config = ryml_get_file_copy(configPath.c_str()); + ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(config)); + + // Create a FalseTarget object for each target in the config file + for (auto target_node : tree["targets"].children()) + { + if (target_node["state"].val() == VALID_STATE[0]) + { + try + { + targets.push_back(FalseTarget(target_node, fs)); + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } + } + } +} + +std::complex TgtGen::process(IqData *ref_buffer) +{ + std::complex response = std::complex(0, 0); + // loop through each target + for (auto target : targets) + { + response += target.process(ref_buffer); + } + + return response; +} + +std::string FalseTarget::get_type() +{ + return type; +} + +double FalseTarget::get_range() +{ + return range; +} + +double FalseTarget::get_doppler() +{ + return doppler; +} + +double FalseTarget::get_rcs() +{ + return rcs; +} + +double FalseTarget::get_delay() +{ + return delay; +} + +u_int32_t FalseTarget::get_id() +{ + return id; +} + +FalseTarget::FalseTarget(c4::yml::NodeRef target_node, uint32_t _fs) +{ + + target_node["id"] >> id; + target_node["type"] >> type; + fs = _fs; + + if (type == TgtGen::VALID_TYPE[0]) + { + target_node["location"]["range"] >> range; + delay = range / 3e8; + delay_samples = delay * fs; + target_node["velocity"]["doppler"] >> doppler; + target_node["rcs"] >> rcs; + } + else + { + throw std::invalid_argument("Invalid target type"); + } +} + +std::complex FalseTarget::process(IqData *buffer) +{ + uint32_t buffer_length = buffer->get_length(); + std::complex response = 0; + try + { + response = Conversions::db2lin(rcs) * buffer->get_sample(buffer_length - delay_samples); + response *= std::exp(std::polar(1, 2 * M_PI * doppler * buffer_length / fs)); + } + catch (const std::exception &e) + { + } + + return response; +} + +std::string ryml_get_file_copy(const char *filename) +{ + std::ifstream in(filename, std::ios::in | std::ios::binary); + if (!in) + { + std::cerr << "could not open " << filename << std::endl; + exit(1); + } + std::ostringstream contents; + contents << in.rdbuf(); + return contents.str(); +} \ No newline at end of file diff --git a/src/capture/iqsimulator/TgtGen.h b/src/capture/iqsimulator/TgtGen.h new file mode 100644 index 0000000..2aa549d --- /dev/null +++ b/src/capture/iqsimulator/TgtGen.h @@ -0,0 +1,115 @@ +/// @file TgtGen.h +/// @class TgtGen +/// @brief A class to generate false targets. + +/// @details +/// Static Targets: remain at a fixed range/delay and Doppler. + +/// @author bennysomers +/// @todo Simulate a false target moving in radar coordinates +/// @todo Simulate a false target moving in spatial coordinates + +#ifndef TGTGEN_H +#define TGTGEN_H + +#include "data/IqData.h" +#include "utilities/Conversions.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +class FalseTarget +{ +private: + /// @brief fs + uint32_t fs; + + /// @brief Target type. + std::string type; + + /// @brief Target delay + double delay; + + /// @brief Target delay in samples + uint32_t delay_samples; + + /// @brief Target range + double range; + + /// @brief Target Doppler + double doppler; + + /// @brief Target RCS + double rcs; + + /// @brief Target ID + u_int32_t id; + +public: + /// @brief Constructor for targets. + /// @return The object. + FalseTarget(c4::yml::NodeRef target_node, uint32_t _fs); + + /// @brief Generate the signal from a false target. + /// @param buffer Pointer to reference buffer. + /// @return Target reflection signal. + std::complex process(IqData *buffer); + + /// @brief Getter for target type. + /// @return Target type. + std::string get_type(); + + /// @brief Getter for target range.type + /// @return Target range. + double get_range(); + + /// @brief Getter for target Doppler. + /// @return Target Doppler. + double get_doppler(); + + /// @brief Getter for target RCS. + /// @return Target RCS. + double get_rcs(); + + /// @brief Getter for target delay. + /// @return Target delay. + double get_delay(); + + /// @brief Getter for target id. + /// @return Target id. + u_int32_t get_id(); +}; + +class TgtGen +{ +private: + /// @brief Vector of false targets. + std::vector targets; + +public: + /// @brief The valid false target types. + static const std::string VALID_TYPE[1]; + + /// @brief The valid false target states. + static const std::string VALID_STATE[1]; + + /// @brief Constructor. + /// @return The object. + TgtGen(std::string configPath, uint32_t fs); + + /// @brief Generate the signal from all false targets. + /// @param ref_buffer Pointer to reference buffer. + /// @return Targets reflection signal. + std::complex process(IqData *ref_buffer); +}; + +#endif +std::string ryml_get_file(const char *filename); \ No newline at end of file diff --git a/src/data/IqData.cpp b/src/data/IqData.cpp index ff32c74..2dfa710 100644 --- a/src/data/IqData.cpp +++ b/src/data/IqData.cpp @@ -39,6 +39,11 @@ std::deque> IqData::get_data() return *data; } +std::complex IqData::get_sample(int64_t index) +{ + return data->at(index); +} + void IqData::push_back(std::complex sample) { if (data->size() < n) diff --git a/src/data/IqData.h b/src/data/IqData.h index f45e363..530ad8f 100644 --- a/src/data/IqData.h +++ b/src/data/IqData.h @@ -66,6 +66,11 @@ public: /// @return IQ data. std::deque> get_data(); + /// @brief Getter for single sample. + /// @param index Index of sample. + /// @return Sample at index. + std::complex get_sample(int64_t index); + /// @brief Push a sample to the queue. /// @param sample A single sample. /// @return Void. diff --git a/src/utilities/Conversions.cpp b/src/utilities/Conversions.cpp new file mode 100644 index 0000000..4694715 --- /dev/null +++ b/src/utilities/Conversions.cpp @@ -0,0 +1,11 @@ +#include "Conversions.h" + +double Conversions::db2lin(double db) +{ + return pow(10, db / 10); +} + +double Conversions::lin2db(double lin) +{ + return 10 * log10(lin); +} \ No newline at end of file diff --git a/src/utilities/Conversions.h b/src/utilities/Conversions.h new file mode 100644 index 0000000..a6d540a --- /dev/null +++ b/src/utilities/Conversions.h @@ -0,0 +1,27 @@ +/// @file Conversions.h +/// @class Conversions +/// @brief A class to convert between different units. + +/// @author bennysomers +/// @todo Add more conversions + +#ifndef CONVERSIONS_H +#define CONVERSIONS_H + +#include + +class Conversions +{ +public: + /// @brief Convert from dB to linear. + /// @param db Value in dB. + /// @return Value in linear. + static double db2lin(double db); + + /// @brief Convert from linear to dB. + /// @param lin Value in linear. + /// @return Value in dB. + static double lin2db(double lin); +}; + +#endif \ No newline at end of file