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 += "
- " + target.type + "
";
+ if (target.type === "static") {
+ output += "- delay: " + target.delay + "
";
+ output += "- delay_samples: " + target.delay_samples + "
";
+ output += "- range: " + target.range + "
";
+ output += "- doppler: " + target.doppler + "
";
+ output += "- rcs: " + target.rcs + "
";
+ } else if (target.type === "moving_radar") {
+ output += "- delay: " + target.delay + "
";
+ output += "- delay_samples: " + target.delay_samples + "
";
+ output += "- range: " + target.range + "
";
+ output += "- start_range: " + target.start_range + "
";
+ output += "- doppler: " + target.doppler + "
";
+ output += "- doppler_rate: " + target.doppler_rate + "
";
+ output += "- rcs: " + target.rcs + "
";
+ }
+ 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