diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c8f69a..afd19f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,8 @@ add_executable(blah2 src/capture/Source.cpp 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 @@ -57,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/api/server.js b/api/server.js index eae752d..6b2e15d 100644 --- a/api/server.js +++ b/api/server.js @@ -6,6 +6,7 @@ var stash_map = require('./stash/maxhold.js'); var stash_detection = require('./stash/detection.js'); var stash_iqdata = require('./stash/iqdata.js'); var stash_timing = require('./stash/timing.js'); +var stash_falsetargets = require('./stash/falsetargets.js'); // constants const PORT = 3000; @@ -16,6 +17,7 @@ var track = ''; var timestamp = ''; var timing = ''; var iqdata = ''; +var falsetargets = ''; var data = ''; var data_map; var data_detection; @@ -23,12 +25,13 @@ var data_tracker; var data_timestamp; var data_timing; var data_iqdata; +var data_falsetargets; var capture = false; // api server const app = express(); // header on all requests -app.use(function(req, res, next) { +app.use(function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); res.header('Expires', '-1'); @@ -56,6 +59,9 @@ app.get('/api/timing', (req, res) => { app.get('/api/iqdata', (req, res) => { res.send(iqdata); }); +app.get('/api/falsetargets', (req, res) => { + res.send(falsetargets); +}); // stash API app.get('/stash/map', (req, res) => { @@ -70,6 +76,9 @@ app.get('/stash/iqdata', (req, res) => { app.get('/stash/timing', (req, res) => { res.send(stash_timing.get_data_timing()); }); +app.get('/stash/falsetargets', (req, res) => { + res.send(stash_falsetargets.get_data_falsetargets()); +}); // read state of capture app.get('/capture', (req, res) => { @@ -85,100 +94,111 @@ app.listen(PORT, HOST, () => { }); // tcp listener map -const server_map = net.createServer((socket)=>{ - socket.write("Hello From Server!") - socket.on("data",(msg)=>{ - data_map = data_map + msg.toString(); - if (data_map.slice(-1) === "}") - { - map = data_map; - data_map = ''; - } - }); - socket.on("close",()=>{ - console.log("Connection closed."); - }) +const server_map = net.createServer((socket) => { + socket.write("Hello From Server!") + socket.on("data", (msg) => { + data_map = data_map + msg.toString(); + if (data_map.slice(-1) === "}") { + map = data_map; + data_map = ''; + } + }); + socket.on("close", () => { + console.log("Connection closed."); + }) }); server_map.listen(3001); // tcp listener detection -const server_detection = net.createServer((socket)=>{ +const server_detection = net.createServer((socket) => { socket.write("Hello From Server!") - socket.on("data",(msg)=>{ - data_detection = data_detection + msg.toString(); - if (data_detection.slice(-1) === "}") - { - detection = data_detection; - data_detection = ''; - } + socket.on("data", (msg) => { + data_detection = data_detection + msg.toString(); + if (data_detection.slice(-1) === "}") { + detection = data_detection; + data_detection = ''; + } }); - socket.on("close",()=>{ - console.log("Connection closed."); + socket.on("close", () => { + console.log("Connection closed."); }) }); server_detection.listen(3002); // tcp listener tracker -const server_tracker = net.createServer((socket)=>{ +const server_tracker = net.createServer((socket) => { socket.write("Hello From Server!") - socket.on("data",(msg)=>{ - data_tracker = data_tracker + msg.toString(); - if (data_tracker.slice(-1) === "}") - { - track = data_tracker; - data_tracker = ''; - } + socket.on("data", (msg) => { + data_tracker = data_tracker + msg.toString(); + if (data_tracker.slice(-1) === "}") { + track = data_tracker; + data_tracker = ''; + } }); - socket.on("close",()=>{ - console.log("Connection closed."); + socket.on("close", () => { + console.log("Connection closed."); }) }); server_tracker.listen(3003); // tcp listener timestamp -const server_timestamp = net.createServer((socket)=>{ +const server_timestamp = net.createServer((socket) => { socket.write("Hello From Server!") - socket.on("data",(msg)=>{ + socket.on("data", (msg) => { data_timestamp = data_timestamp + msg.toString(); timestamp = data_timestamp; data_timestamp = ''; }); - socket.on("close",()=>{ - console.log("Connection closed."); + socket.on("close", () => { + console.log("Connection closed."); }) }); server_timestamp.listen(4000); // tcp listener timing -const server_timing = net.createServer((socket)=>{ +const server_timing = net.createServer((socket) => { socket.write("Hello From Server!") - socket.on("data",(msg)=>{ + socket.on("data", (msg) => { data_timing = data_timing + msg.toString(); - if (data_timing.slice(-1) === "}") - { + if (data_timing.slice(-1) === "}") { timing = data_timing; data_timing = ''; } }); - socket.on("close",()=>{ - console.log("Connection closed."); + socket.on("close", () => { + console.log("Connection closed."); }) }); server_timing.listen(4001); // tcp listener iqdata metadata -const server_iqdata = net.createServer((socket)=>{ +const server_iqdata = net.createServer((socket) => { socket.write("Hello From Server!") - socket.on("data",(msg)=>{ + socket.on("data", (msg) => { data_iqdata = data_iqdata + msg.toString(); - if (data_iqdata.slice(-1) === "}") - { + if (data_iqdata.slice(-1) === "}") { iqdata = data_iqdata; data_iqdata = ''; } }); - socket.on("close",()=>{ - console.log("Connection closed."); + socket.on("close", () => { + console.log("Connection closed."); }) }); server_iqdata.listen(4002); + +// tcp listener falsetargets +const server_falsetargets = net.createServer((socket) => { + socket.write("Hello From Server!") + socket.on("data", (msg) => { + data_falsetargets = data_falsetargets + msg.toString(); + if (data_falsetargets.slice(-1) === "}") { + falsetargets = data_falsetargets; + data_falsetargets = ''; + } + }); + socket.on("close", () => { + console.log("Connection closed."); + }) +}); +server_falsetargets.listen(4003); diff --git a/api/stash/falsetargets.js b/api/stash/falsetargets.js new file mode 100644 index 0000000..72de7b9 --- /dev/null +++ b/api/stash/falsetargets.js @@ -0,0 +1,50 @@ +const http = require('http'); + +var falsetargets = []; +frequency = []; +var ts = ''; +var output = []; +const options_falsetargets = { + host: '127.0.0.1', + path: '/api/falsetargets', + port: 3000 +}; + +function update_data() { + + // check if timestamp is updated + http.get(options_falsetargets, function (res) { + res.setEncoding('utf8'); + res.on('data', function (body) { + if (ts != body) { + ts = body; + http.get(options_falsetargets, function (res) { + let body_map = ''; + res.setEncoding('utf8'); + res.on('data', (chunk) => { + body_map += chunk; + }); + res.on('end', () => { + try { + + output = JSON.parse(body_map); + // false targets + falsetargets.push(output.falsetargets); + } catch (e) { + console.error(e.message); + } + }); + }); + } + }); + }); + +}; + +setInterval(update_data, 100); + +function get_data() { + return output; +}; + +module.exports.get_data_falsetargets = get_data; \ No newline at end of file diff --git a/config/config.yml b/config/config.yml index 47b6c8b..e7eb5b3 100644 --- a/config/config.yml +++ b/config/config.yml @@ -2,7 +2,7 @@ capture: fs: 2000000 fc: 204640000 device: - type: "RspDuo" + type: "IqSimulator" replay: state: false loop: true @@ -14,13 +14,13 @@ process: buffer: 1.5 overlap: 0 ambiguity: - delayMin: -10 - delayMax: 400 - dopplerMin: -200 - dopplerMax: 200 + delayMin: -10 # bins + delayMax: 400 # bins + dopplerMin: -200 # Hz + dopplerMax: 200 # Hz clutter: - delayMin: -10 - delayMax: 400 + delayMin: -10 # bins + delayMax: 400 # bins detection: pfa: 0.00001 nGuard: 2 @@ -46,6 +46,7 @@ network: timestamp: 4000 timing: 4001 iqdata: 4002 + falsetargets: 4003 truth: asdb: @@ -58,7 +59,7 @@ truth: port: 30001 save: - iq: true + iq: false map: false detection: false timing: false diff --git a/config/false_targets.yml b/config/false_targets.yml new file mode 100644 index 0000000..aeb9481 --- /dev/null +++ b/config/false_targets.yml @@ -0,0 +1,28 @@ +targets: + - id: 0 + 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: 1 + type: "static" + location: + range: 30000 # meters + velocity: + doppler: -150 # Hertz + rcs: -20 # dBsm + state: "active" + + - id: 2 + type: "moving_radar" + location: + range: 5000 # meters + velocity: + doppler: 100 # Hertz + dopplerRate: 0 # Hertz/second + rcs: -20 # dBsm - this is also contrived + state: "active" \ No newline at end of file diff --git a/html/controller/index.html b/html/controller/index.html index 24818c9..9ec0841 100644 --- a/html/controller/index.html +++ b/html/controller/index.html @@ -38,6 +38,7 @@
  • Detections in delay-Doppler over time
  • Spectrum reference
  • Timing display
  • +
  • False Target Data
  • diff --git a/html/display/falsetargets/index.html b/html/display/falsetargets/index.html new file mode 100644 index 0000000..5f03537 --- /dev/null +++ b/html/display/falsetargets/index.html @@ -0,0 +1,25 @@ + + + + + + blah2 + + + + + + + + +
    +
    +
    +
    + + + + diff --git a/html/js/table_falsetargets.js b/html/js/table_falsetargets.js new file mode 100644 index 0000000..81f4cbb --- /dev/null +++ b/html/js/table_falsetargets.js @@ -0,0 +1,60 @@ + +var host = window.location.hostname; +var isLocalHost = (host === "localhost" || host === "127.0.0.1" || host === "192.168.0.112"); + +// setup API +var urlFalseTargets; + +if (isLocalHost) { + urlFalseTargets = '//' + host + ':3000/api/falsetargets'; +} else { + urlFalseTargets = '//' + host + '/api/falsetargets'; +} + +//callback function +var intervalId = window.setInterval(function () { + $.getJSON(urlFalseTargets, function (data) { + if (data != null) { + var table = document.getElementById("data"); + + + // PLEASE SOMEONE FORMAT THIS NICER! // + var output = ""; + data.false_targets.forEach((target) => { + output += "id: " + target.id + "
    "; + output += ""; + }); + table.innerHTML = output; + // data.false_targets.foreach((targetjson) => { + // target = JSON.parse(targetjson); + // console.log(target); + // }); + + // for (var i = 0; i < data.length; i++) { + // var row = table.insertRow(i + 1); + // var cell1 = row.insertCell(0); + // var cell2 = row.insertCell(1); + // var cell3 = row.insertCell(2); + // cell1.innerHTML = data[i].x; + // cell2.innerHTML = data[i].y; + // cell3.innerHTML = data[i].z; + // } + } + }); +}, 100); \ No newline at end of file diff --git a/src/capture/Capture.cpp b/src/capture/Capture.cpp index cf04fa5..170e6a7 100644 --- a/src/capture/Capture.cpp +++ b/src/capture/Capture.cpp @@ -4,9 +4,10 @@ #include #include #include +#include "iqsimulator/IqSimulator.h" // constants -const std::string Capture::VALID_TYPE[2] = {"RspDuo", "Usrp"}; +const std::string Capture::VALID_TYPE[3] = {"RspDuo", "Usrp", "IqSimulator"}; // constructor Capture::Capture(std::string _type, uint32_t _fs, uint32_t _fc, std::string _path) @@ -26,7 +27,8 @@ void Capture::process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config) std::unique_ptr device = factory_source(type, config); // capture status thread - std::thread t1([&]{ + std::thread t1([&] + { while (true) { httplib::Client cli("http://127.0.0.1:3000"); @@ -46,8 +48,7 @@ void Capture::process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config) } } sleep(1); - } - }); + } }); if (!replay) { @@ -58,39 +59,44 @@ void Capture::process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config) { device->replay(buffer1, buffer2, file, loop); } - } -std::unique_ptr Capture::factory_source(const std::string& type, c4::yml::NodeRef config) +std::unique_ptr Capture::factory_source(const std::string &type, c4::yml::NodeRef config) { - if (type == VALID_TYPE[0]) - { - return std::make_unique(type, fc, fs, path, &saveIq); - } - else if (type == VALID_TYPE[1]) - { - std::string address, subdev; - std::vector antenna; - std::vector gain; - std::string _antenna; - double _gain; - config["address"] >> address; - config["subdev"] >> subdev; - config["antenna"][0] >> _antenna; - antenna.push_back(_antenna); - config["antenna"][1] >> _antenna; - antenna.push_back(_antenna); - config["gain"][0] >> _gain; - gain.push_back(_gain); - config["gain"][1] >> _gain; - gain.push_back(_gain); - - return std::make_unique(type, fc, fs, path, &saveIq, - address, subdev, antenna, gain); - } - // Handle unknown type - std::cerr << "Error: Source type does not exist." << std::endl; - return nullptr; + if (type == VALID_TYPE[0]) // RspDuo + { + return std::make_unique(type, fc, fs, path, &saveIq); + } + else if (type == VALID_TYPE[1]) // Usrp + { + std::string address, subdev; + std::vector antenna; + std::vector gain; + std::string _antenna; + double _gain; + config["address"] >> address; + config["subdev"] >> subdev; + config["antenna"][0] >> _antenna; + antenna.push_back(_antenna); + config["antenna"][1] >> _antenna; + antenna.push_back(_antenna); + config["gain"][0] >> _gain; + gain.push_back(_gain); + config["gain"][1] >> _gain; + gain.push_back(_gain); + + return std::make_unique(type, fc, fs, path, &saveIq, + address, subdev, antenna, gain); + } + else if (type == VALID_TYPE[2]) // IqSimulator + { + uint32_t n_min; + n_min = 2000000; + return std::make_unique(type, fc, fs, path, &saveIq, n_min); + } + // Handle unknown type + std::cerr << "Error: Source type does not exist." << std::endl; + return nullptr; } void Capture::set_replay(bool _loop, std::string _file) diff --git a/src/capture/Capture.h b/src/capture/Capture.h index 760d5bf..ecb4c3d 100644 --- a/src/capture/Capture.h +++ b/src/capture/Capture.h @@ -19,7 +19,7 @@ class Capture { private: /// @brief The valid capture devices. - static const std::string VALID_TYPE[2]; + static const std::string VALID_TYPE[3]; /// @brief The capture device type. std::string type; diff --git a/src/capture/iqsimulator/IqSimulator.cpp b/src/capture/iqsimulator/IqSimulator.cpp new file mode 100644 index 0000000..cce5c7a --- /dev/null +++ b/src/capture/iqsimulator/IqSimulator.cpp @@ -0,0 +1,67 @@ +#include "IqSimulator.h" + +// constructor +IqSimulator::IqSimulator(std::string _type, uint32_t _fc, uint32_t _fs, + std::string _path, bool *_saveIq, + uint32_t _n_min = 1000, + std::string _falseTargetsConfigFilePath, + std::string _configFilePath) + : Source(_type, _fc, _fs, _path, _saveIq) +{ + n_min = _n_min; + u_int64_t total_samples = 0; + false_targets_config_file_path = _falseTargetsConfigFilePath; + config_file_path = _configFilePath; +} + +void IqSimulator::start() +{ +} + +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, config_file_path, fs, fc); + while (true) + { + 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()); + std::uniform_real_distribution<> dis(-(2 ^ 14), 2 ^ 14); + + buffer1->lock(); + buffer2->lock(); + for (uint16_t i = 0; i < samples_per_iteration; i++) + { + + buffer1->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(); + } + } +} + +void IqSimulator::replay(IqData *buffer1, IqData *buffer2, std::string file, bool loop) +{ +} \ No newline at end of file diff --git a/src/capture/iqsimulator/IqSimulator.h b/src/capture/iqsimulator/IqSimulator.h new file mode 100644 index 0000000..223a210 --- /dev/null +++ b/src/capture/iqsimulator/IqSimulator.h @@ -0,0 +1,84 @@ +/// @file IqSimulator.h +/// @class IqSimulator +/// @brief A class to generate simulated IQ data with false targets +/// @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 +/// @todo Simulate false targets +/// @todo Simulate realistic target motion +/// @todo Simulate N channels, instead of just 2 + +#ifndef IQSIMULATOR_H +#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 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; + + /// @brief Path to the radar configuration file. + std::string config_file_path; + +public: + /// @brief Constructor. + /// @param type Type of source. = "IQSimulator" + /// @param fc Center frequency (Hz). + /// @param fs Sample rate (Hz). + /// @param path Path to save IQ data. + /// @param saveIq Pointer to boolean to save IQ data. + /// @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, + std::string false_targets_config_file_path = "config/false_targets.yml", + std::string config_file_path = "config/config.yml"); + + /// @brief Implement capture function on IQSimulator. + /// @param buffer1 Pointer to reference buffer. + /// @param buffer2 Pointer to surveillance buffer. + /// @return Void. + void process(IqData *buffer1, IqData *buffer2); + + /// @brief Call methods to start capture. + /// @return Void. + void start(); + + /// @brief Call methods to gracefully stop capture. + /// @return Void. + void stop(); + + /// @brief Implement replay function on IQSimulator. + /// @param buffer1 Pointer to reference buffer. + /// @param buffer2 Pointer to surveillance buffer. + /// @param file Path to file to replay data from. + /// @param loop True if samples should loop at EOF. + /// @return Void. + void replay(IqData *buffer1, IqData *buffer2, std::string file, bool loop); +}; + +#endif \ No newline at end of file diff --git a/src/capture/iqsimulator/TgtGen.cpp b/src/capture/iqsimulator/TgtGen.cpp new file mode 100644 index 0000000..521a052 --- /dev/null +++ b/src/capture/iqsimulator/TgtGen.cpp @@ -0,0 +1,226 @@ +#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[2] = {"static", "moving_radar"}; +const std::string TgtGen::VALID_STATE[1] = {"active"}; + +// constructor +TgtGen::TgtGen(std::string false_tgt_config_path, std::string config_path, uint32_t fs, uint32_t fc) +{ + // Read in the false targets config file + std::string config = ryml_get_file_copy(false_tgt_config_path.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, fc)); + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } + } + } + + // Create the socket using details from the config file. + config = ryml_get_file_copy(config_path.c_str()); + tree = ryml::parse_in_arena(ryml::to_csubstr(config)); + + std::string ip; + uint16_t port; + + tree["network"]["ip"] >> ip; + tree["network"]["ports"]["falsetargets"] >> port; + + socket = new Socket(ip, port); + + sample_counter = 0; +} + +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); + } + + // output false target truth + if (sample_counter % 100000 == 0) + { + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType &allocator = document.GetAllocator(); + rapidjson::Value json_false_targets(rapidjson::kArrayType); + for (auto target : targets) + { + json_false_targets.PushBack(target.to_json(allocator), allocator); + } + + document.AddMember("false_targets", json_false_targets, allocator); + rapidjson::StringBuffer strbuf; + rapidjson::Writer writer(strbuf); + writer.SetMaxDecimalPlaces(2); + document.Accept(writer); + + socket->sendData(strbuf.GetString()); + } + + sample_counter++; + + return response; +} + +double FalseTarget::get_range() +{ + return range; +} + +void FalseTarget::set_range(double _range) +{ + range = _range; + delay = range / Constants::c; + delay_samples = delay * fs; +} + +double FalseTarget::get_delay() +{ + return delay; +} + +void FalseTarget::set_delay(double _delay) +{ + delay = _delay; + range = delay * Constants::c; + delay_samples = delay * fs; +} + +FalseTarget::FalseTarget(c4::yml::NodeRef target_node, uint32_t _fs, uint32_t _fc) +{ + + target_node["id"] >> id; + target_node["type"] >> type; + fs = _fs; + fc = _fc; + sample_counter = 0; + + if (type == TgtGen::VALID_TYPE[0]) + { + target_node["location"]["range"] >> range; + delay = range / Constants::c; + delay_samples = delay * fs; + target_node["velocity"]["doppler"] >> doppler; + target_node["rcs"] >> rcs; + } + else if (type == TgtGen::VALID_TYPE[1]) + { + target_node["location"]["range"] >> range; + start_range = range; + delay = range / Constants::c; + delay_samples = delay * fs; + target_node["velocity"]["doppler"] >> doppler; + target_node["velocity"]["dopplerRate"] >> doppler_rate; + target_node["rcs"] >> rcs; + } + else + { + throw std::invalid_argument("Invalid target type"); + } +} + +std::complex FalseTarget::process(IqData *ref_buffer) +{ + uint32_t buffer_length = ref_buffer->get_length(); + std::complex response = 0; + try + { + response = Conversions::db2lin(rcs) * ref_buffer->get_sample(buffer_length - delay_samples); + response *= std::exp(std::polar(1, 2 * M_PI * doppler * buffer_length / fs)); + + if (type == TgtGen::VALID_TYPE[1]) + { + double range_rate = -1 * doppler * Constants::c / 2.0 / fc; + set_range(range + (range_rate / fs)); + + // very basic PD controller + // will need tuning for different fs + doppler_rate += 0.0000001 * (range - start_range) / start_range; // target tends towards start_range + // doppler_rate -= doppler / std::abs(doppler) / std::max(std::abs(doppler), 0.1) / 100; // target tends towards 0 Doppler + doppler_rate = std::clamp(doppler_rate, -5.0, 5.0); // clamp to a reasonable value + doppler += doppler_rate / fs; // update doppler + doppler = std::clamp(doppler, + std::max(-range, -250.0), + std::min(range, 250.0)); // clamp to range + } + + sample_counter++; + } + catch (const std::exception &e) + { + } + + return response; +} + +rapidjson::Value FalseTarget::to_json(rapidjson::Document::AllocatorType &allocator) +{ + + rapidjson::Value target(rapidjson::kObjectType); + + target.AddMember("id", id, allocator); + target.AddMember("type", rapidjson::Value(type.c_str(), allocator).Move(), allocator); + + try + { + if (type == TgtGen::VALID_TYPE[0]) + { + target.AddMember("range", range, allocator); + target.AddMember("delay", delay, allocator); + target.AddMember("delay_samples", delay_samples, allocator); + target.AddMember("doppler", doppler, allocator); + target.AddMember("rcs", rcs, allocator); + } + else if (type == TgtGen::VALID_TYPE[1]) + { + target.AddMember("range", range, allocator); + target.AddMember("start_range", start_range, allocator); + target.AddMember("delay", delay, allocator); + target.AddMember("delay_samples", delay_samples, allocator); + target.AddMember("doppler", doppler, allocator); + target.AddMember("doppler_rate", doppler_rate, allocator); + target.AddMember("rcs", rcs, allocator); + } + else + { + throw std::invalid_argument("Invalid target type"); + } + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } + return target; +} + +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..42966e5 --- /dev/null +++ b/src/capture/iqsimulator/TgtGen.h @@ -0,0 +1,143 @@ +/// @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 "data/meta/Constants.h" +#include "process/utility/Socket.h" + +#include +#include +#include + +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/filewritestream.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +class FalseTarget +{ +private: + /// @brief fs + uint32_t fs; + + /// @brief fc + uint32_t fc; + + /// @brief Target delay + double delay; + + /// @brief Target delay in samples + uint32_t delay_samples; + + /// @brief Target range + double range; + + /// @brief Target starting range + double start_range; + + /// @brief Sample counter + uint64_t sample_counter; + +public: + /// @brief Target type. + std::string type; + + /// @brief Target Doppler + double doppler; + + /// @brief Target Doppler Rate + double doppler_rate; + + /// @brief Target RCS + double rcs; + + /// @brief Target ID + u_int32_t id; + + /// @brief Constructor for targets. + /// @return The object. + FalseTarget(c4::yml::NodeRef target_node, uint32_t _fs, uint32_t _fc); + + /// @brief Generate the signal from a false target. + /// @param ref_buffer Pointer to reference buffer. + /// @return Target reflection signal. + std::complex process(IqData *ref_buffer); + + /// @brief Getter for range. + /// @return Range in meters. + double get_range(); + + /// @brief Setter for range. + /// @param range Range in meters. + void set_range(double range); + + /// @brief Getter for delay. + /// @return Delay in seconds. + double get_delay(); + + /// @brief Setter for delay. + /// @param delay Delay in seconds. + void set_delay(double delay); + + /// @brief Outputs false target truth as JSON + /// @return JSON string. + rapidjson::Value to_json(rapidjson::Document::AllocatorType &allocator); +}; + +class TgtGen +{ +private: + /// @brief Vector of false targets. + std::vector targets; + + /// @brief Socket to send false target data. + Socket *socket; + + /// @brief Sample counter + uint64_t sample_counter; + +public: + /// @brief The valid false target types. + static const std::string VALID_TYPE[2]; + + /// @brief The valid false target states. + static const std::string VALID_STATE[1]; + + /// @brief Constructor. + /// @param false_tgt_config_path Path to false targets configuration file. + /// @param config_path Path to blah2 config file. + /// @param fs Sample rate (Hz). + /// @param fc Center frequency (Hz). + /// @return The object. + TgtGen(std::string false_tgt_config_path, std::string config_path, uint32_t fs, uint32_t fc); + + /// @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 @@ class IqData /// @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