21 |
22 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/html/display/detection/delay-doppler/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
21 |
22 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 30hours
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/process/meta/HammingNumber.cpp:
--------------------------------------------------------------------------------
1 | #include "HammingNumber.h"
2 |
3 | bool HammingNumber::operator!=(const HammingNumber &other) const
4 | {
5 | return true;
6 | }
7 |
8 | HammingNumber HammingNumber::begin() const
9 | {
10 | return *this;
11 | }
12 |
13 | HammingNumber HammingNumber::end() const
14 | {
15 | return *this;
16 | }
17 |
18 | unsigned int HammingNumber::operator*() const
19 | {
20 | return x.back();
21 | }
22 |
23 | HammingNumber::HammingNumber(const std::vector
&pfs)
24 | : H(pfs), hp(pfs.size(), 0), hv({pfs}), x({1}) {}
25 |
26 | const HammingNumber &HammingNumber::operator++()
27 | {
28 | for (std::vector::size_type i = 0; i < H.size(); i++)
29 | for (; hv[i] <= x.back(); hv[i] = x[++hp[i]] * H[i])
30 | ;
31 | x.push_back(hv[0]);
32 | for (std::vector::size_type i = 1; i < H.size(); i++)
33 | if (hv[i] < x.back())
34 | x.back() = hv[i];
35 | return *this;
36 | }
37 |
38 | uint32_t next_hamming(uint32_t value)
39 | {
40 | for (auto i : HammingNumber({2, 3, 5}))
41 | {
42 | if (i > value)
43 | {
44 | return i;
45 | }
46 | }
47 | return 0;
48 | }
49 |
--------------------------------------------------------------------------------
/src/capture/rspduo/README.md:
--------------------------------------------------------------------------------
1 | ## Config File
2 |
3 | The source of truth for the config file parameters is the [SDRplay API Specification](https://www.sdrplay.com/docs/SDRplay_API_Specification_v3.pdf).
4 |
5 | Here is a list of available config parameters:
6 |
7 | - **agcSetPoint** in dBfs has a default value of -60 dBfs, and can be set between -72 dBfs and 0 dBfs.
8 |
9 | - **bandwidthNumber** in Hz has a default value of 50 Hz, and can be 0, 5, 50 or 100 Hz. This is the number of times per second the AGC makes gain adjustments by changing the gain reduction value. If setting to 0 then the AGC is effectively disabled.
10 |
11 | - **gainReduction** in dB has a default value of 40 dB, and can be set between 20 and 59. This is the initial value the gain reduction is set to, before the AGC changes this parameter to approach the AGC set point.
12 |
13 | - **lnaState** has a default value of 4, must be between 1 and 9. A larger number means a larger gain reduction (attenuation). Maximum gain at LNA state 1 and minimum gain at LNA state 9.
14 |
15 | - **dabNotch** is a bool, true turns on the DAB band notch filter (default false).
16 |
17 | - **rfNotch** is a bool, true turns on the AM/FM band notch filter (default false).
18 |
19 |
--------------------------------------------------------------------------------
/docker/Dockerfile-kraken:
--------------------------------------------------------------------------------
1 | FROM ubuntu:22.04 as blah2_env
2 | LABEL maintainer="30hours "
3 | LABEL org.opencontainers.image.source https://github.com/30hours/blah2
4 |
5 | WORKDIR /blah2
6 | ADD lib lib
7 | RUN apt-get update && apt-get install -y software-properties-common \
8 | && apt-get update \
9 | && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y \
10 | g++ make cmake git curl zip unzip doxygen graphviz \
11 | libfftw3-dev pkg-config gfortran \
12 | libusb-dev libusb-1.0.0-dev \
13 | && apt-get autoremove -y \
14 | && apt-get clean -y \
15 | && rm -rf /var/lib/apt/lists/*
16 |
17 | # install RTL-SDR API
18 | RUN git clone https://github.com/krakenrf/librtlsdr /opt/librtlsdr \
19 | && cd /opt/librtlsdr && mkdir build && cd build \
20 | && cmake ../ -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON && make && make install && ldconfig
21 |
22 | FROM blah2_env as blah2
23 | LABEL maintainer="30hours "
24 |
25 | ADD src src
26 | ADD test test
27 | ADD CMakeLists.txt CMakePresets.json Doxyfile /blah2/
28 | RUN mkdir -p build && cd build && cmake -S . --preset prod-release \
29 | -DCMAKE_PREFIX_PATH=$(echo /blah2/lib/vcpkg_installed/*/share) .. \
30 | && cd prod-release && make
31 | RUN chmod +x bin/blah2
32 |
--------------------------------------------------------------------------------
/src/process/utility/Socket.h:
--------------------------------------------------------------------------------
1 | /// @file Socket.h
2 | /// @class Socket
3 | /// @brief A class to implement network socket functionality.
4 | /// @details Used to pass radar data from app to the API.
5 | /// @author 30hours
6 |
7 | #ifndef SOCKET_H
8 | #define SOCKET_H
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | class Socket
15 | {
16 | private:
17 | /// @brief Common io_context for all socket objects.
18 | static asio::io_context io_context;
19 |
20 | /// @brief Common MTU size for all socket objects.
21 | static const uint32_t MTU;
22 |
23 | /// @brief The ASIO endpoint.
24 | asio::ip::tcp::endpoint endpoint;
25 |
26 | /// @brief The ASIO socket.
27 | asio::ip::tcp::socket socket;
28 |
29 | public:
30 | /// @brief Constructor for Socket.
31 | /// @param ip IP address of data destination.
32 | /// @param port Port of data destination.
33 | /// @return The object.
34 | Socket(const std::string& ip, uint16_t port);
35 |
36 | /// @brief Destructor.
37 | /// @return Void.
38 | ~Socket();
39 |
40 | /// @brief Helper function to send data in chunks.
41 | /// @param data String of complete data to send.
42 | /// @return Void.
43 | void sendData(const std::string& data);
44 |
45 | };
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/src/process/detection/Centroid.h:
--------------------------------------------------------------------------------
1 | /// @file Centroid.h
2 | /// @class Centroid
3 | /// @brief A class to remove duplicate target detections.
4 | /// @details If detection SNR is larger than neighbours, then remove.
5 | /// @author 30hours
6 |
7 | #ifndef CENTROID_H
8 | #define CENTROID_H
9 |
10 | #include "data/Detection.h"
11 | #include
12 | #include
13 |
14 | class Centroid
15 | {
16 | private:
17 | /// @brief Number of delay bins to check.
18 | uint16_t nDelay;
19 |
20 | /// @brief Number of Doppler bins to check.
21 | uint16_t nDoppler;
22 |
23 | /// @brief Doppler resolution to convert Hz to bins (Hz).
24 | double resolutionDoppler;
25 |
26 | /// @brief Pointer to detection data to store result.
27 | Detection *detection;
28 |
29 | public:
30 | /// @brief Constructor.
31 | /// @param nDelay Number of delay bins to check.
32 | /// @param nDoppler Number of Doppler bins to check.
33 | /// @param resolutionDoppler Doppler resolution to convert Hz to bins (Hz).
34 | /// @return The object.
35 | Centroid(uint16_t nDelay, uint16_t nDoppler, double resolutionDoppler);
36 |
37 | /// @brief Destructor.
38 | /// @return Void.
39 | ~Centroid();
40 |
41 | /// @brief Implement the 1D CFAR detector.
42 | /// @param x Detections from the 1D CFAR detector.
43 | /// @return Centroided detections.
44 | std::unique_ptr process(Detection *x);
45 | };
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/src/process/detection/Interpolate.h:
--------------------------------------------------------------------------------
1 | /// @file Interpolate.h
2 | /// @class Interpolate
3 | /// @brief A class to interpolate detection data using a quadratic curve.
4 | /// @details Interpolate in delay and Doppler. If 2 points either side have a higher SNR, then remove detection.
5 | /// References:
6 | /// - https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
7 | /// - Fundamentals of Signal Processing (2nd), Richards, Section 5.3.6
8 | /// @author 30hours
9 | /// @todo Should I remove the detection pointer? Also on Centroid.
10 |
11 | #ifndef INTERPOLATE_H
12 | #define INTERPOLATE_H
13 |
14 | #include "data/Map.h"
15 | #include "data/Detection.h"
16 |
17 | #include
18 |
19 | class Interpolate
20 | {
21 | private:
22 | /// @brief True if interpolating over delay.
23 | bool doDelay;
24 |
25 | /// @brief True if interpolating over Doppler.
26 | bool doDoppler;
27 |
28 | /// @brief Pointer to detection data to store result.
29 | Detection *detection;
30 |
31 | public:
32 | /// @brief Constructor.
33 | /// @param doDelay True if interpolating over delay.
34 | /// @param doDoppler True if interpolating over Doppler.
35 | /// @return The object.
36 | Interpolate(bool doDelay, bool doDoppler);
37 |
38 | /// @brief Destructor.
39 | /// @return Void.
40 | ~Interpolate();
41 |
42 | /// @brief Implement the 1D CFAR detector.
43 | /// @param x Detections from the 1D CFAR detector.
44 | /// @return Interpolated detections.
45 | std::unique_ptr process(Detection *x, Map> *y);
46 | };
47 |
48 | #endif
49 |
--------------------------------------------------------------------------------
/src/data/meta/Timing.h:
--------------------------------------------------------------------------------
1 | /// @file Timing.h
2 | /// @class Timing
3 | /// @brief A class to store timing statistics.
4 | /// @author 30hours
5 |
6 | #ifndef TIMING_H
7 | #define TIMING_H
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | class Timing
14 | {
15 | private:
16 | /// @brief Start time (POSIX ms).
17 | uint64_t tStart;
18 |
19 | /// @brief Current time (POSIX ms).
20 | uint64_t tNow;
21 |
22 | /// @brief Number of CPI's.
23 | uint64_t n;
24 |
25 | /// @brief Time since first CPI (ms).
26 | uint64_t uptime;
27 |
28 | /// @brief Time differences (ms).
29 | std::vector time;
30 |
31 | /// @brief Names of time differences.
32 | std::vector name;
33 |
34 | public:
35 | /// @brief Constructor.
36 | /// @param tStart Start time (POSIX ms).
37 | /// @return The object.
38 | Timing(uint64_t tStart);
39 |
40 | /// @brief Update the time differences and names.
41 | /// @param tNow Current time (POSIX ms).
42 | /// @param time Vector of time differences (ms).
43 | /// @param name Vector of time difference names.
44 | /// @return Void.
45 | void update(uint64_t tNow, std::vector time, std::vector name);
46 |
47 | /// @brief Generate JSON of the map and metadata.
48 | /// @return JSON string.
49 | std::string to_json();
50 |
51 | /// @brief Append the map to a save file.
52 | /// @param json JSON string of map and metadata.
53 | /// @param path Path of file to save.
54 | /// @return True is save is successful.
55 | bool save(std::string json, std::string path);
56 | };
57 |
58 | #endif
59 |
--------------------------------------------------------------------------------
/src/process/spectrum/SpectrumAnalyser.h:
--------------------------------------------------------------------------------
1 | /// @file SpectrumAnalyser.h
2 | /// @class SpectrumAnalyser
3 | /// @brief A class to generate frequency spectrum plots.
4 | /// @details Simple decimate and FFT on CPI IQ data for frequency spectrum.
5 | /// @author 30hours
6 | /// @todo Potentially create k spectrum plots from sub-CPIs.
7 | /// @todo FFT with HammingNumber class.
8 |
9 | #ifndef SPECTRUMANALYSER_H
10 | #define SPECTRUMANALYSER_H
11 |
12 | #include "data/IqData.h"
13 | #include
14 | #include
15 |
16 | class SpectrumAnalyser
17 | {
18 | private:
19 | /// @brief Number of samples on input.
20 | uint32_t n;
21 |
22 | /// @brief Minimum bandwidth of frequency bin (Hz).
23 | double bandwidth;
24 |
25 | /// @brief Decimation factor.
26 | uint32_t decimation;
27 |
28 | /// @brief FFTW plans for ambiguity processing.
29 | fftw_plan fftX;
30 |
31 | /// @brief FFTW storage for ambiguity processing.
32 | std::complex *dataX;
33 |
34 | /// @brief Number of samples to perform FFT.
35 | uint32_t nfft;
36 |
37 | /// @brief Number of samples in decimated spectrum.
38 | uint32_t nSpectrum;
39 |
40 | /// @brief Resolution of spectrum (Hz).
41 | double resolution;
42 |
43 | public:
44 | /// @brief Constructor.
45 | /// @param n Number of samples on input.
46 | /// @param bandwidth Minimum bandwidth of frequency bin (Hz).
47 | /// @return The object.
48 | SpectrumAnalyser(uint32_t n, double bandwidth);
49 |
50 | /// @brief Destructor.
51 | /// @return Void.
52 | ~SpectrumAnalyser();
53 |
54 | /// @brief Process spectrum data.
55 | /// @param x Reference samples.
56 | /// @return Void.
57 | void process(IqData *x);
58 | };
59 |
60 | #endif
--------------------------------------------------------------------------------
/config/config-usrp.yml:
--------------------------------------------------------------------------------
1 | capture:
2 | fs: 2000000
3 | fc: 204640000
4 | device:
5 | type: "Usrp"
6 | address: "localhost"
7 | subdev: "A:A A:B"
8 | antenna: ["RX2", "RX2"]
9 | gain: [20.0, 20.0]
10 | replay:
11 | state: false
12 | loop: true
13 | file: '/opt/blah2/replay/file.rspduo'
14 |
15 | process:
16 | data:
17 | cpi: 0.5
18 | buffer: 1.5
19 | overlap: 0
20 | ambiguity:
21 | delayMin: -10
22 | delayMax: 400
23 | dopplerMin: -200
24 | dopplerMax: 200
25 | clutter:
26 | enable: true
27 | delayMin: -10
28 | delayMax: 400
29 | detection:
30 | enable: true
31 | pfa: 0.00001
32 | nGuard: 2
33 | nTrain: 6
34 | minDelay: 5
35 | minDoppler: 15
36 | nCentroid: 6
37 | tracker:
38 | enable: true
39 | initiate:
40 | M: 3
41 | N: 5
42 | maxAcc: 10
43 | delete: 10
44 | smooth: "none"
45 |
46 | network:
47 | ip: 0.0.0.0
48 | ports:
49 | api: 3000
50 | map: 3001
51 | detection: 3002
52 | track: 3003
53 | timestamp: 4000
54 | timing: 4001
55 | iqdata: 4002
56 | config: 4003
57 |
58 | truth:
59 | adsb:
60 | enabled: false
61 | tar1090: 'adsb.30hours.dev'
62 | adsb2dd: 'adsb2dd.30hours.dev'
63 | ais:
64 | enabled: false
65 | ip: 0.0.0.0
66 | port: 30001
67 |
68 | location:
69 | rx:
70 | latitude: -34.9286
71 | longitude: 138.5999
72 | altitude: 50
73 | name: "Adelaide"
74 | tx:
75 | latitude: -34.9810
76 | longitude: 138.7081
77 | altitude: 750
78 | name: "Mount Lofty"
79 |
80 | save:
81 | iq: true
82 | map: false
83 | detection: false
84 | timing: false
85 | path: "/blah2/save/"
86 |
--------------------------------------------------------------------------------
/config/config-kraken.yml:
--------------------------------------------------------------------------------
1 | capture:
2 | fs: 2000000
3 | fc: 204640000
4 | device:
5 | type: "Kraken"
6 | gain: [15.0, 15.0]
7 | array:
8 | x: [0, 0]
9 | y: [0, 0]
10 | z: [0, 0]
11 | boresight: 0.0
12 | replay:
13 | state: false
14 | loop: true
15 | file: '/opt/blah2/replay/file.kraken'
16 |
17 | process:
18 | data:
19 | cpi: 0.5
20 | buffer: 1.5
21 | overlap: 0
22 | ambiguity:
23 | delayMin: -10
24 | delayMax: 400
25 | dopplerMin: -200
26 | dopplerMax: 200
27 | clutter:
28 | enable: true
29 | delayMin: -10
30 | delayMax: 400
31 | detection:
32 | enable: true
33 | pfa: 0.00001
34 | nGuard: 2
35 | nTrain: 6
36 | minDelay: 5
37 | minDoppler: 15
38 | nCentroid: 6
39 | tracker:
40 | enable: true
41 | initiate:
42 | M: 3
43 | N: 5
44 | maxAcc: 10
45 | delete: 10
46 | smooth: "none"
47 |
48 | network:
49 | ip: 0.0.0.0
50 | ports:
51 | api: 3000
52 | map: 3001
53 | detection: 3002
54 | track: 3003
55 | timestamp: 4000
56 | timing: 4001
57 | iqdata: 4002
58 | config: 4003
59 |
60 | truth:
61 | adsb:
62 | enabled: false
63 | tar1090: 'adsb.30hours.dev'
64 | adsb2dd: 'adsb2dd.30hours.dev'
65 | ais:
66 | enabled: false
67 | ip: 0.0.0.0
68 | port: 30001
69 |
70 | location:
71 | rx:
72 | latitude: -34.9286
73 | longitude: 138.5999
74 | altitude: 50
75 | name: "Adelaide"
76 | tx:
77 | latitude: -34.9810
78 | longitude: 138.7081
79 | altitude: 750
80 | name: "Mount Lofty"
81 |
82 | save:
83 | iq: true
84 | map: false
85 | detection: false
86 | timing: false
87 | path: "/blah2/save/"
88 |
--------------------------------------------------------------------------------
/config/radar4.yml:
--------------------------------------------------------------------------------
1 | capture:
2 | fs: 2000000
3 | fc: 204640000
4 | device:
5 | type: "RspDuo"
6 | agcSetPoint: -20
7 | bandwidthNumber: 5
8 | gainReduction: 59
9 | lnaState: 1
10 | dabNotch: false
11 | rfNotch: false
12 | replay:
13 | state: false
14 | loop: true
15 | file: '/opt/blah2/replay/file.rspduo'
16 |
17 | process:
18 | data:
19 | cpi: 0.5
20 | buffer: 1.5
21 | overlap: 0
22 | ambiguity:
23 | delayMin: -10
24 | delayMax: 400
25 | dopplerMin: -200
26 | dopplerMax: 200
27 | clutter:
28 | enable: true
29 | delayMin: -10
30 | delayMax: 400
31 | detection:
32 | enable: true
33 | pfa: 0.00001
34 | nGuard: 2
35 | nTrain: 6
36 | minDelay: 5
37 | minDoppler: 15
38 | nCentroid: 6
39 | tracker:
40 | enable: true
41 | initiate:
42 | M: 3
43 | N: 5
44 | maxAcc: 10
45 | delete: 10
46 | smooth: "none"
47 |
48 | network:
49 | ip: 0.0.0.0
50 | ports:
51 | api: 3000
52 | map: 3001
53 | detection: 3002
54 | track: 3003
55 | timestamp: 4000
56 | timing: 4001
57 | iqdata: 4002
58 | config: 4003
59 |
60 | truth:
61 | adsb:
62 | enabled: false
63 | tar1090: 'adsb.30hours.dev'
64 | adsb2dd: 'adsb2dd.30hours.dev'
65 | ais:
66 | enabled: false
67 | ip: 0.0.0.0
68 | port: 30001
69 |
70 | location:
71 | rx:
72 | latitude: -34.9286
73 | longitude: 138.5999
74 | altitude: 50
75 | name: "Adelaide"
76 | tx:
77 | latitude: -34.9810
78 | longitude: 138.7081
79 | altitude: 750
80 | name: "Mount Lofty"
81 |
82 | save:
83 | iq: true
84 | map: false
85 | detection: false
86 | timing: false
87 | path: "/blah2/save/"
88 |
--------------------------------------------------------------------------------
/api/stash/timing.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | var nCpi = 20;
4 | var ts = '';
5 | var cpi = [];
6 | var output = {};
7 | const options_timestamp = {
8 | host: '127.0.0.1',
9 | path: '/api/timestamp',
10 | port: 3000
11 | };
12 | const options_iqdata = {
13 | host: '127.0.0.1',
14 | path: '/api/timing',
15 | port: 3000
16 | };
17 |
18 | function update_data(callback) {
19 | http.get(options_timestamp, function (res) {
20 | res.setEncoding('utf8');
21 | res.on('data', function (body) {
22 | if (ts != body) {
23 | ts = body;
24 | http.get(options_iqdata, function (res) {
25 | let body_map = '';
26 | res.setEncoding('utf8');
27 | res.on('data', (chunk) => {
28 | body_map += chunk;
29 | });
30 | res.on('end', () => {
31 | try {
32 | cpi = JSON.parse(body_map);
33 | keys = Object.keys(cpi);
34 | keys = keys.filter(item => item !== "uptime");
35 | keys = keys.filter(item => item !== "nCpi");
36 | for (i = 0; i < keys.length; i++) {
37 | if (!(keys[i] in output)) {
38 | output[keys[i]] = [];
39 | }
40 | output[keys[i]].push(cpi[keys[i]]);
41 | if (output[keys[i]].length > nCpi) {
42 | output[keys[i]].shift();
43 | }
44 | }
45 | } catch (e) {
46 | console.error(e.message);
47 | }
48 | });
49 | });
50 | }
51 | });
52 | });
53 | }
54 |
55 | setInterval(update_data, 100);
56 |
57 | function get_data() {
58 | return output;
59 | }
60 |
61 | module.exports.get_data_timing = get_data;
62 |
--------------------------------------------------------------------------------
/host/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes auto;
3 |
4 | error_log /var/log/nginx/error.log warn;
5 | pid /var/run/nginx.pid;
6 |
7 | events {
8 | worker_connections 1024;
9 | }
10 |
11 | http {
12 | default_type application/octet-stream;
13 | include /etc/nginx/mime.types;
14 |
15 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
16 | '$status $body_bytes_sent "$http_referer" '
17 | '"$http_user_agent" "$http_x_forwarded_for"';
18 |
19 | access_log /var/log/nginx/access.log main;
20 |
21 | sendfile on;
22 | keepalive_timeout 65;
23 |
24 | include /etc/nginx/conf.d/*.conf;
25 |
26 | server {
27 | listen 80 default_server;
28 | listen [::]:80 default_server;
29 | include /etc/nginx/mime.types;
30 |
31 | set $backend_ip localhost;
32 | set $domain_name localhost;
33 |
34 | proxy_pass_header Content-Type;
35 | proxy_set_header X-Real-IP $domain_name;
36 | proxy_set_header X-Forwarded-For $domain_name;
37 | proxy_set_header Host $domain_name;
38 | proxy_http_version 1.1;
39 | proxy_set_header Connection "";
40 | proxy_connect_timeout 1;
41 | proxy_next_upstream error timeout http_500 http_502 http_503 http_504 http_404;
42 | proxy_intercept_errors on;
43 |
44 | location / {
45 | proxy_pass http://$backend_ip:49152;
46 | }
47 |
48 | location ~ ^/(maxhold|api|stash)/(.*) {
49 | proxy_pass http://$backend_ip:3000/$1/$2;
50 | }
51 |
52 | error_page 501 502 503 504 =200 /error.html;
53 | location = /error.html {
54 | root /usr/local/apache2/htdocs;
55 | }
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/config/config-hackrf.yml:
--------------------------------------------------------------------------------
1 | capture:
2 | fs: 2000000
3 | fc: 204640000
4 | device:
5 | type: "HackRF"
6 | serial:
7 | - "REFERENCE_DEVICE_SERIAL_NUMBER"
8 | - "SURVILLANCE_DEVICE_SERIAL_NUMBER"
9 | gain_lna: [32, 32]
10 | gain_vga: [30, 30]
11 | amp_enable: [false, false]
12 | replay:
13 | state: false
14 | loop: true
15 | file: '/opt/blah2/replay/file.hackrf'
16 |
17 | process:
18 | data:
19 | cpi: 0.5
20 | buffer: 1.5
21 | overlap: 0
22 | ambiguity:
23 | delayMin: -10
24 | delayMax: 400
25 | dopplerMin: -200
26 | dopplerMax: 200
27 | clutter:
28 | enable: true
29 | delayMin: -10
30 | delayMax: 400
31 | detection:
32 | enable: true
33 | pfa: 0.00001
34 | nGuard: 2
35 | nTrain: 6
36 | minDelay: 5
37 | minDoppler: 15
38 | nCentroid: 6
39 | tracker:
40 | enable: true
41 | initiate:
42 | M: 3
43 | N: 5
44 | maxAcc: 10
45 | delete: 10
46 | smooth: "none"
47 |
48 | network:
49 | ip: 0.0.0.0
50 | ports:
51 | api: 3000
52 | map: 3001
53 | detection: 3002
54 | track: 3003
55 | timestamp: 4000
56 | timing: 4001
57 | iqdata: 4002
58 | config: 4003
59 |
60 | truth:
61 | adsb:
62 | enabled: false
63 | tar1090: 'adsb.30hours.dev'
64 | adsb2dd: 'adsb2dd.30hours.dev'
65 | ais:
66 | enabled: false
67 | ip: 0.0.0.0
68 | port: 30001
69 |
70 | location:
71 | rx:
72 | latitude: -34.9286
73 | longitude: 138.5999
74 | altitude: 50
75 | name: "Adelaide"
76 | tx:
77 | latitude: -34.9810
78 | longitude: 138.7081
79 | altitude: 750
80 | name: "Mount Lofty"
81 |
82 | save:
83 | iq: true
84 | map: false
85 | detection: false
86 | timing: false
87 | path: "/blah2/save/"
88 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # TODO: update devcontainer with vcpkg manifest
2 |
3 | # ubuntu-22.04 by default
4 | ARG VARIANT="jammy"
5 | FROM mcr.microsoft.com/vscode/devcontainers/cpp:0-${VARIANT}
6 | LABEL maintainer="30hours "
7 |
8 | ENV DEBIAN_FRONTEND=noninteractive
9 |
10 | WORKDIR /blah2
11 | ADD lib lib
12 | RUN apt-get update && apt-get install -y software-properties-common \
13 | && apt-add-repository ppa:ettusresearch/uhd \
14 | && apt-get update \
15 | && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y \
16 | g++ make cmake git curl zip unzip doxygen graphviz \
17 | libfftw3-dev pkg-config gfortran \
18 | libuhd-dev=4.6.0.0-0ubuntu1~jammy1 \
19 | uhd-host=4.6.0.0-0ubuntu1~jammy1 \
20 | && apt-get autoremove -y \
21 | && apt-get clean -y \
22 | && rm -rf /var/lib/apt/lists/*
23 |
24 | # install dependencies from vcpkg
25 | RUN git clone https://github.com/microsoft/vcpkg /opt/vcpkg \
26 | && /opt/vcpkg/bootstrap-vcpkg.sh
27 | ENV PATH="/opt/vcpkg:${PATH}" VCPKG_ROOT=/opt/vcpkg
28 | RUN cd /blah2/lib && vcpkg integrate install \
29 | && vcpkg install --clean-after-build
30 |
31 | # install SDRplay API
32 | RUN chmod +x /blah2/lib/sdrplay-3.14.0/SDRplay_RSP_API-Linux-3.14.0.run \
33 | && /blah2/lib/sdrplay-3.14.0/SDRplay_RSP_API-Linux-3.14.0.run --tar -xvf -C /blah2/lib/sdrplay-3.14.0 \
34 | && cp /blah2/lib/sdrplay-3.14.0/x86_64/libsdrplay_api.so.3.14 /usr/local/lib/libsdrplay_api.so \
35 | && cp /blah2/lib/sdrplay-3.14.0/x86_64/libsdrplay_api.so.3.14 /usr/local/lib/libsdrplay_api.so.3.14 \
36 | && cp /blah2/lib/sdrplay-3.14.0/inc/* /usr/local/include \
37 | && chmod 644 /usr/local/lib/libsdrplay_api.so /usr/local/lib/libsdrplay_api.so.3.14 \
38 | && ldconfig
39 |
40 | # install UHD API
41 | RUN uhd_images_downloader
42 |
43 |
--------------------------------------------------------------------------------
/config/config.yml:
--------------------------------------------------------------------------------
1 | capture:
2 | fs: 2000000
3 | fc: 204640000
4 | device:
5 | type: "RspDuo"
6 | agcSetPoint: -20
7 | # 0 to disable AGC
8 | #bandwidthNumber: 0
9 | bandwidthNumber: 5
10 | gainReduction: [50, 45]
11 | lnaState: 1
12 | dabNotch: false
13 | rfNotch: false
14 | replay:
15 | state: false
16 | loop: true
17 | file: '/opt/blah2/replay/file.rspduo'
18 |
19 | process:
20 | data:
21 | cpi: 0.75
22 | buffer: 2
23 | overlap: 0
24 | ambiguity:
25 | delayMin: -10
26 | delayMax: 400
27 | dopplerMin: -200
28 | dopplerMax: 200
29 | clutter:
30 | enable: true
31 | delayMin: -10
32 | delayMax: 400
33 | detection:
34 | enable: true
35 | pfa: 0.00001
36 | nGuard: 2
37 | nTrain: 6
38 | minDelay: 5
39 | minDoppler: 15
40 | nCentroid: 6
41 | tracker:
42 | enable: false
43 | initiate:
44 | M: 3
45 | N: 5
46 | maxAcc: 10
47 | delete: 10
48 | smooth: "none"
49 |
50 | network:
51 | ip: 0.0.0.0
52 | ports:
53 | api: 3000
54 | map: 3001
55 | detection: 3002
56 | track: 3003
57 | timestamp: 4000
58 | timing: 4001
59 | iqdata: 4002
60 | config: 4003
61 |
62 | truth:
63 | adsb:
64 | enabled: true
65 | tar1090: 'adsb.30hours.dev'
66 | adsb2dd: 'adsb2dd.30hours.dev'
67 | ais:
68 | enabled: false
69 | ip: 0.0.0.0
70 | port: 30001
71 |
72 | location:
73 | rx:
74 | latitude: -34.9286
75 | longitude: 138.5999
76 | altitude: 50
77 | name: "Adelaide"
78 | tx:
79 | latitude: -34.9810
80 | longitude: 138.7081
81 | altitude: 750
82 | name: "Mount Lofty"
83 |
84 | save:
85 | iq: true
86 | map: false
87 | detection: false
88 | timing: false
89 | path: "/blah2/save/"
90 |
--------------------------------------------------------------------------------
/src/capture/Source.cpp:
--------------------------------------------------------------------------------
1 | #include "Source.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | Source::Source()
11 | {
12 | }
13 |
14 | // constructor
15 | Source::Source(std::string _type, uint32_t _fc, uint32_t _fs,
16 | std::string _path, bool *_saveIq)
17 | {
18 | type = _type;
19 | fc = _fc;
20 | fs = _fs;
21 | path = _path;
22 | saveIq = _saveIq;
23 | }
24 |
25 | std::string Source::open_file()
26 | {
27 | // get string of timestamp in YYYYmmdd-HHMMSS
28 | auto currentTime = std::chrono::system_clock::to_time_t(
29 | std::chrono::system_clock::now());
30 | std::tm* timeInfo = std::localtime(¤tTime);
31 | std::ostringstream oss;
32 | oss << std::put_time(timeInfo, "%Y%m%d-%H%M%S");
33 | std::string timestamp = oss.str();
34 |
35 | // create file path
36 | std::string typeLower = type;
37 | std::transform(typeLower.begin(), typeLower.end(),
38 | typeLower.begin(), ::tolower);
39 | std::string file = path + timestamp + "." + typeLower + ".iq";
40 |
41 | saveIqFile.open(file, std::ios::binary);
42 |
43 | if (!saveIqFile.is_open())
44 | {
45 | std::cerr << "Error: Can not open file: " << file << std::endl;
46 | exit(1);
47 | }
48 | std::cout << "Ready to record IQ to file: " << file << std::endl;
49 |
50 | return file;
51 | }
52 |
53 | void Source::close_file()
54 | {
55 | if (!saveIqFile.is_open())
56 | {
57 | saveIqFile.close();
58 | }
59 |
60 | // switch member with blank file stream
61 | std::ofstream blankFile;
62 | std::swap(saveIqFile, blankFile);
63 | }
64 |
65 | void Source::kill()
66 | {
67 | if (type == "RspDuo")
68 | {
69 | stop();
70 | } else if (type == "HackRF")
71 | {
72 | stop();
73 | }
74 | exit(0);
75 | }
76 |
--------------------------------------------------------------------------------
/src/process/spectrum/SpectrumAnalyser.cpp:
--------------------------------------------------------------------------------
1 | #include "SpectrumAnalyser.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | // constructor
9 | SpectrumAnalyser::SpectrumAnalyser(uint32_t _n, double _bandwidth)
10 | {
11 | // input
12 | n = _n;
13 | bandwidth = _bandwidth;
14 |
15 | // compute nfft
16 | decimation = n/bandwidth;
17 | nSpectrum = n/decimation;
18 | nfft = nSpectrum*decimation;
19 |
20 | // compute FFTW plans in constructor
21 | dataX = new std::complex[nfft];
22 | fftX = fftw_plan_dft_1d(nfft, reinterpret_cast(dataX),
23 | reinterpret_cast(dataX), FFTW_FORWARD, FFTW_ESTIMATE);
24 | }
25 |
26 | SpectrumAnalyser::~SpectrumAnalyser()
27 | {
28 | fftw_destroy_plan(fftX);
29 | }
30 |
31 | void SpectrumAnalyser::process(IqData *x)
32 | {
33 | // load data and FFT
34 | uint32_t i;
35 | std::deque> data = x->get_data();
36 | for (i = 0; i < nfft; i++)
37 | {
38 | dataX[i] = data[i];
39 | }
40 | fftw_execute(fftX);
41 |
42 | // fftshift
43 | std::vector> fftshift;
44 | for (i = 0; i < nfft; i++)
45 | {
46 | fftshift.push_back(dataX[(i + int(nfft / 2) + 1) % nfft]);
47 | }
48 |
49 | // decimate
50 | std::vector> spectrum;
51 | for (i = 0; i < nfft; i+=decimation)
52 | {
53 | spectrum.push_back(fftshift[i]);
54 | }
55 | x->update_spectrum(spectrum);
56 |
57 | // update frequency
58 | std::vector frequency;
59 | double offset = 0;
60 | if (decimation % 2 == 0)
61 | {
62 | offset = bandwidth/2;
63 | }
64 | for (i = -nSpectrum/2; i < nSpectrum/2; i++)
65 | {
66 | frequency.push_back(((i*bandwidth)+offset+204640000)/1000);
67 | }
68 | x->update_frequency(frequency);
69 |
70 | return;
71 | }
72 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | # blah2 Test
2 |
3 | A set of tests are provided for development/debugging.
4 |
5 | ## Framework
6 |
7 | The test framework is [catch2](https://github.com/catchorg/Catch2).
8 |
9 | ## Types
10 |
11 | The test files are split across directories defined by the type of test.
12 |
13 | - **Unit tests** will test the class in isolation. The directory structure mirrors *src*.
14 | - **Functional tests** will test that expected outputs are achieved from defined inputs. An example would be checking the program turns a specific IQ data set to a specific delay-Doppler map. This test category will rely on golden data.
15 | - **Comparison tests** will compare different methods of performing the same task. An example would be comparing 2 methods of clutter filtering. Metrics to be compared may include time and performance. Note there is no specific pass/fail criteria for comparison tests - this is purely for information. A comparison test will pass if executed successfully. Any comparison testing on input parameters for a single class will be handled in the unit test.
16 |
17 | ## Usage
18 |
19 | All tests are compiled when building, however tests be run manually.
20 |
21 | - Run a single unit test for "TestClass".
22 |
23 | ```
24 | sudo docker exec -it blah2 /blah2/bin/test/unit/testClass
25 | ```
26 |
27 | - Run a single functional test for "TestFunctional".
28 |
29 | ```
30 | sudo docker exec -it blah2 /blah2/bin/test/functional/testFunctional
31 | ```
32 |
33 | - Run a single comparison test for "TestComparison".
34 |
35 | ```
36 | sudo docker exec -it blah2 /blah2/bin/test/comparison/testComparison
37 | ```
38 |
39 | - *TODO:* Run all test cases.
40 |
41 | ```
42 | sudo docker exec -it blah2 /blah2/bin/test/runall.sh
43 | sudo docker exec -it blah2 /blah2/bin/test/unit/runall.sh
44 | sudo docker exec -it blah2 /blah2/bin/test/functional/runall.sh
45 | sudo docker exec -it blah2 /blah2/bin/test/comparison/runall.sh
46 | ```
--------------------------------------------------------------------------------
/api/stash/maxhold.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | var nCpi = 20;
4 | var map = [];
5 | var maxhold = '';
6 | var timestamp = '';
7 | const options_timestamp = {
8 | host: '127.0.0.1',
9 | path: '/api/timestamp',
10 | port: 3000
11 | };
12 | const options_map = {
13 | host: '127.0.0.1',
14 | path: '/api/map',
15 | port: 3000
16 | };
17 |
18 | function process(matrixArray) {
19 |
20 | const result = [];
21 |
22 | for (let i = 0; i < matrixArray[0].length; i++) {
23 | const row = [];
24 | for (let j = 0; j < matrixArray[0][0].length; j++) {
25 | let maxVal = matrixArray[0][i][j];
26 | for (let k = 1; k < matrixArray.length; k++) {
27 | maxVal = Math.max(maxVal, matrixArray[k][i][j]);
28 | }
29 | row.push(maxVal);
30 | }
31 | result.push(row);
32 | }
33 |
34 | return result;
35 | }
36 |
37 | function update_data() {
38 |
39 | // check if timestamp is updated
40 | http.get(options_timestamp, function(res) {
41 | res.setEncoding('utf8');
42 | res.on('data', function (body) {
43 | if (timestamp != body)
44 | {
45 | timestamp = body;
46 | http.get(options_map, function(res) {
47 | let body_map = '';
48 | res.setEncoding('utf8');
49 | res.on('data', (chunk) => {
50 | body_map += chunk;
51 | });
52 | res.on('end', () => {
53 | try {
54 | maxhold = JSON.parse(body_map);
55 | map.push(maxhold.data);
56 | if (map.length > nCpi) {
57 | map.shift();
58 | }
59 | maxhold.data = process(map);
60 | } catch (e) {
61 | console.error(e.message);
62 | }
63 | });
64 | });
65 | }
66 | });
67 | });
68 |
69 | };
70 |
71 | setInterval(update_data, 100);
72 |
73 | function get_data() {
74 | return maxhold;
75 | };
76 |
77 | module.exports.get_data_map = get_data;
--------------------------------------------------------------------------------
/src/process/detection/CfarDetector1D.h:
--------------------------------------------------------------------------------
1 | /// @file CfarDetector1D.h
2 | /// @class CfarDetector1D
3 | /// @brief A class to implement a 1D CFAR detector.
4 | /// @details Converts an AmbiguityMap to DetectionData. 1D CFAR operates across delay, to minimise detections from the zero-Doppler line.
5 | /// @author 30hours
6 | /// @todo Actually implement the min delay and Doppler.
7 |
8 | #ifndef CFARDETECTOR1D_H
9 | #define CFARDETECTOR1D_H
10 |
11 | #include "data/Map.h"
12 | #include "data/Detection.h"
13 | #include
14 | #include
15 | #include
16 |
17 | class CfarDetector1D
18 | {
19 | private:
20 | /// @brief Probability of false alarm, numeric in [0,1]
21 | double pfa;
22 |
23 | /// @brief Number of single-sided guard cells.
24 | int8_t nGuard;
25 |
26 | /// @brief Number of single-sided training cells.
27 | int8_t nTrain;
28 |
29 | /// @brief Minimum delay to process detections (bins).
30 | int8_t minDelay;
31 |
32 | /// @brief Minimum absolute Doppler to process detections (Hz).
33 | double minDoppler;
34 |
35 | /// @brief Pointer to detection data to store result.
36 | Detection *detection;
37 |
38 | public:
39 | /// @brief Constructor.
40 | /// @param pfa Probability of false alarm, numeric in [0,1].
41 | /// @param nGuard Number of single-sided guard cells.
42 | /// @param nTrain Number of single-sided training cells.
43 | /// @param minDelay Minimum delay to process detections (bins).
44 | /// @param minDoppler Minimum absolute Doppler to process detections (Hz).
45 | /// @return The object.
46 | CfarDetector1D(double pfa, int8_t nGuard, int8_t nTrain, int8_t minDelay, double minDoppler);
47 |
48 | /// @brief Destructor.
49 | /// @return Void.
50 | ~CfarDetector1D();
51 |
52 | /// @brief Implement the 1D CFAR detector.
53 | /// @param x Ambiguity map data of IQ samples.
54 | /// @return Detections from the 1D CFAR detector.
55 | std::unique_ptr process(Map> *x);
56 | };
57 |
58 | #endif
59 |
--------------------------------------------------------------------------------
/api/stash/iqdata.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | var nCpi = 20;
4 | var spectrum = [];
5 | frequency = [];
6 | var timestamp = [];
7 | var ts = '';
8 | var output = [];
9 | const options_timestamp = {
10 | host: '127.0.0.1',
11 | path: '/api/timestamp',
12 | port: 3000
13 | };
14 | const options_iqdata = {
15 | host: '127.0.0.1',
16 | path: '/api/iqdata',
17 | port: 3000
18 | };
19 |
20 | function update_data() {
21 |
22 | // check if timestamp is updated
23 | http.get(options_timestamp, function(res) {
24 | res.setEncoding('utf8');
25 | res.on('data', function (body) {
26 | if (ts != body)
27 | {
28 | ts = body;
29 | http.get(options_iqdata, function(res) {
30 | let body_map = '';
31 | res.setEncoding('utf8');
32 | res.on('data', (chunk) => {
33 | body_map += chunk;
34 | });
35 | res.on('end', () => {
36 | try {
37 | output = JSON.parse(body_map);
38 | // spectrum
39 | spectrum.push(output.spectrum);
40 | if (spectrum.length > nCpi) {
41 | spectrum.shift();
42 | }
43 | output.spectrum = spectrum;
44 | // frequency
45 | frequency.push(output.frequency);
46 | if (frequency.length > nCpi) {
47 | frequency.shift();
48 | }
49 | output.frequency = frequency;
50 | // timestamp
51 | timestamp.push(output.timestamp);
52 | if (timestamp.length > nCpi) {
53 | timestamp.shift();
54 | }
55 | output.timestamp = timestamp;
56 | } catch (e) {
57 | console.error(e.message);
58 | }
59 | });
60 | });
61 | }
62 | });
63 | });
64 |
65 | };
66 |
67 | setInterval(update_data, 100);
68 |
69 | function get_data() {
70 | return output;
71 | };
72 |
73 | module.exports.get_data_iqdata = get_data;
--------------------------------------------------------------------------------
/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | blah2
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
34 |
35 |
40 |
43 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/process/detection/Centroid.cpp:
--------------------------------------------------------------------------------
1 | #include "Centroid.h"
2 | #include
3 | #include
4 | #include
5 |
6 | // constructor
7 | Centroid::Centroid(uint16_t _nDelay, uint16_t _nDoppler, double _resolutionDoppler)
8 | {
9 | // input
10 | nDelay = _nDelay;
11 | nDoppler = _nDoppler;
12 | resolutionDoppler = _resolutionDoppler;
13 | }
14 |
15 | Centroid::~Centroid()
16 | {
17 | }
18 |
19 | std::unique_ptr Centroid::process(Detection *x)
20 | {
21 | // store detections temporarily
22 | std::vector delay, doppler, snr;
23 | delay = x->get_delay();
24 | doppler = x->get_doppler();
25 | snr = x->get_snr();
26 |
27 | // centroid data
28 | uint16_t delayMin, delayMax;
29 | double dopplerMin, dopplerMax;
30 | bool isCentroid;
31 | std::vector delay2, doppler2, snr2;
32 |
33 | // loop over every detection
34 | for (size_t i = 0; i < snr.size(); i++)
35 | {
36 | delayMin = (int)(delay[i]) - nDelay;
37 | delayMax = (int)(delay[i]) + nDelay;
38 | dopplerMin = doppler[i] - (nDoppler * resolutionDoppler);
39 | dopplerMax = doppler[i] + (nDoppler * resolutionDoppler);
40 | isCentroid = true;
41 |
42 | // find detections to keep
43 | for (size_t j = 0; j < snr.size(); j++)
44 | {
45 | // skip same detection
46 | if (j == i)
47 | {
48 | continue;
49 | }
50 | // search detections close by
51 | if (delay[j] > delayMin && delay[j] < delayMax &&
52 | doppler[j] > dopplerMin && doppler[j] < dopplerMax)
53 | {
54 | // remove if SNR is lower
55 | if (snr[i] < snr[j])
56 | {
57 | isCentroid = false;
58 | break;
59 | }
60 | }
61 | }
62 | // store centroided detections
63 | if (isCentroid)
64 | {
65 | delay2.push_back(delay[i]);
66 | doppler2.push_back(doppler[i]);
67 | snr2.push_back(snr[i]);
68 | }
69 | }
70 |
71 | // create detection
72 | return std::make_unique(delay2, doppler2, snr2);
73 | }
74 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose
3 | {
4 | "name": "blah2 dev",
5 |
6 | // Update the 'dockerComposeFile' list if you have more compose files or use different names.
7 | // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
8 | "dockerComposeFile": [
9 | "docker-compose.yml"
10 | ],
11 |
12 | // The 'service' property is the name of the service for the container that VS Code should
13 | // use. Update this value and .devcontainer/docker-compose.yml to the real service name.
14 | "service": "blah2-dev",
15 |
16 | // The optional 'workspaceFolder' property is the path VS Code should open by default when
17 | // connected. This is typically a file mount in .devcontainer/docker-compose.yml
18 | "workspaceFolder": "/workspace",
19 |
20 | // Features to add to the dev container. More info: https://containers.dev/features.
21 | // "features": {},
22 |
23 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
24 | // "forwardPorts": [],
25 |
26 | // Uncomment the next line if you want start specific services in your Docker Compose config.
27 | // "runServices": [],
28 |
29 | // Uncomment the next line if you want to keep your containers running after VS Code shuts down.
30 | // "shutdownAction": "none",
31 |
32 | // Uncomment the next line to run commands after the container is created.
33 | // "postCreateCommand": "cat /etc/os-release",
34 |
35 | // Configure tool-specific properties.
36 | // "customizations": {},
37 | "customizations": {
38 | "vscode": {
39 | // Add the IDs of extensions you want installed when the container is created.
40 | "extensions": [
41 | "ms-vscode.cpptools-extension-pack",
42 | ],
43 | }
44 | },
45 |
46 | // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
47 | "remoteUser": "vscode"
48 | }
49 |
--------------------------------------------------------------------------------
/docker/Dockerfile-uhd:
--------------------------------------------------------------------------------
1 | FROM ubuntu:22.04 as blah2_env
2 | LABEL maintainer="30hours "
3 | LABEL org.opencontainers.image.source https://github.com/30hours/blah2
4 |
5 | WORKDIR /blah2
6 | ADD lib lib
7 | RUN apt-get update && apt-get install -y software-properties-common \
8 | && apt-add-repository ppa:ettusresearch/uhd \
9 | && apt-get update \
10 | && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y \
11 | g++ make cmake git curl zip unzip doxygen graphviz \
12 | libfftw3-dev pkg-config gfortran \
13 | ccache dpdk libboost-all-dev libdpdk-dev \
14 | libudev-dev libusb-1.0.0-dev python3-dev \
15 | python3-docutils python3-mako python3-numpy \
16 | python3-pip python3-requests \
17 | && apt-get autoremove -y \
18 | && apt-get clean -y \
19 | && rm -rf /var/lib/apt/lists/*
20 |
21 | # install UHD from source
22 | ENV UHD_TAG=v4.6.0.0
23 | RUN git clone https://github.com/EttusResearch/uhd.git /uhd \
24 | && cd /uhd/ && git checkout $UHD_TAG \
25 | && mkdir -p /uhd/host/build \
26 | && cd /uhd/host/build \
27 | && cmake .. -DENABLE_PYTHON3=ON -DUHD_RELEASE_MODE=release -DCMAKE_INSTALL_PREFIX=/usr \
28 | && make -j $(echo nproc) \
29 | && make test \
30 | && make install
31 |
32 | # install dependencies from vcpkg
33 | ENV VCPKG_ROOT=/opt/vcpkg
34 | RUN export PATH="/opt/vcpkg:${PATH}" \
35 | && git clone https://github.com/microsoft/vcpkg /opt/vcpkg \
36 | && if [ "$(uname -m)" = "aarch64" ]; then export VCPKG_FORCE_SYSTEM_BINARIES=1; fi \
37 | && /opt/vcpkg/bootstrap-vcpkg.sh -disableMetrics \
38 | && cd /blah2/lib && vcpkg integrate install \
39 | && vcpkg install --clean-after-build
40 |
41 | # install UHD API
42 | RUN uhd_images_downloader
43 |
44 | FROM blah2_env as blah2
45 | LABEL maintainer="30hours "
46 |
47 | ADD src src
48 | ADD test test
49 | ADD CMakeLists.txt CMakePresets.json Doxyfile /blah2/
50 | RUN mkdir -p build && cd build && cmake -S . --preset prod-release \
51 | -DCMAKE_PREFIX_PATH=$(echo /blah2/lib/vcpkg_installed/*/share) .. \
52 | && cd prod-release && make
53 | RUN chmod +x bin/blah2
54 |
--------------------------------------------------------------------------------
/src/data/Detection.h:
--------------------------------------------------------------------------------
1 | /// @file Detection.h
2 | /// @class Detection
3 | /// @brief A class to store detection data.
4 | /// @author 30hours
5 |
6 | #ifndef DETECTION_H
7 | #define DETECTION_H
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | class Detection
14 | {
15 | private:
16 | /// @brief Detections in delay (bins).
17 | std::vector delay;
18 |
19 | /// @brief Detections in Doppler (Hz).
20 | std::vector doppler;
21 |
22 | /// @brief Detections in SNR.
23 | std::vector snr;
24 |
25 | public:
26 | /// @brief Constructor.
27 | /// @param delay Detections in delay (bins).
28 | /// @param doppler Detections in Doppler (Hz).
29 | /// @return The object.
30 | Detection(std::vector delay, std::vector doppler, std::vector snr);
31 |
32 | /// @brief Constructor for single detection.
33 | /// @param delay Detection in delay (bins).
34 | /// @param doppler Detection in Doppler (Hz).
35 | /// @return The object.
36 | Detection(double delay, double doppler, double snr);
37 |
38 | /// @brief Get detections in delay.
39 | /// @return Detections in delay (bins).
40 | std::vector get_delay();
41 |
42 | /// @brief Get detections in Doppler.
43 | /// @return Detections in Doppler (Hz).
44 | std::vector get_doppler();
45 |
46 | /// @brief Detections in SNR.
47 | /// @return Detections in SNR.
48 | std::vector get_snr();
49 |
50 | /// @brief Get number of detections.
51 | /// @return Number of detections
52 | size_t get_nDetections();
53 |
54 | /// @brief Generate JSON of the detections and metadata.
55 | /// @param timestamp Current time (POSIX ms).
56 | /// @return JSON string.
57 | std::string to_json(uint64_t timestamp);
58 |
59 | /// @brief Update JSON to convert delay bins to km.
60 | /// @param json Input JSON string with delay field.
61 | /// @param fs Sampling frequency (Hz).
62 | /// @return JSON string.
63 | std::string delay_bin_to_km(std::string json, uint32_t fs);
64 |
65 | /// @brief Append the detections to a save file.
66 | /// @param json JSON string of detections and metadata.
67 | /// @param path Path of file to save.
68 | /// @return True is save is successful.
69 | bool save(std::string json, std::string path);
70 | };
71 |
72 | #endif
73 |
--------------------------------------------------------------------------------
/src/capture/usrp/Usrp.h:
--------------------------------------------------------------------------------
1 | /// @file Usrp.h
2 | /// @class Usrp
3 | /// @brief A class to capture data on the Ettus Research USRP.
4 | /// @details Uses the UHD C API to extract samples into the processing chain.
5 | ///
6 | /// Should work on all USRP models.
7 | /// Networked models require an IP address in the config file.
8 | /// Requires a USB 3.0 cable for higher data rates.
9 | ///
10 | /// @author 30hours
11 | /// @todo Add replay to Usrp.
12 | /// @todo Fix single overflow per CPI.
13 | /// @todo Fix occasional timeout ERROR_CODE_TIMEOUT.
14 |
15 | #ifndef USRP_H
16 | #define USRP_H
17 |
18 | #include "capture/Source.h"
19 | #include "data/IqData.h"
20 |
21 | #include
22 | #include
23 |
24 | class Usrp : public Source
25 | {
26 | private:
27 |
28 | /// @brief Address of USRP device.
29 | /// @details "localhost" if USB, else IP address.
30 | std::string address;
31 |
32 | /// @brief Subdevice string for USRP.
33 | /// @details See docs.
34 | std::string subdev;
35 |
36 | /// @brief Antenna string for each channel.
37 | std::vector antenna;
38 |
39 | /// @brief USRP gain for each channel.
40 | std::vector gain;
41 |
42 | public:
43 |
44 | /// @brief Constructor.
45 | /// @param fc Center frequency (Hz).
46 | /// @param path Path to save IQ data.
47 | /// @return The object.
48 | Usrp(std::string type, uint32_t fc, uint32_t fs, std::string path,
49 | bool *saveIq, std::string address, std::string subdev,
50 | std::vector antenna, std::vector gain);
51 |
52 | /// @brief Implement capture function on USRP.
53 | /// @param buffer1 Pointer to reference buffer.
54 | /// @param buffer2 Pointer to surveillance buffer.
55 | /// @return Void.
56 | void process(IqData *buffer1, IqData *buffer2);
57 |
58 | /// @brief Call methods to start capture.
59 | /// @return Void.
60 | void start();
61 |
62 | /// @brief Call methods to gracefully stop capture.
63 | /// @return Void.
64 | void stop();
65 |
66 | /// @brief Implement replay function on RSPduo.
67 | /// @param buffer1 Pointer to reference buffer.
68 | /// @param buffer2 Pointer to surveillance buffer.
69 | /// @param file Path to file to replay data from.
70 | /// @param loop True if samples should loop at EOF.
71 | /// @return Void.
72 | void replay(IqData *buffer1, IqData *buffer2, std::string file, bool loop);
73 |
74 | };
75 |
76 | #endif
--------------------------------------------------------------------------------
/api/stash/detection.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | var time = 300;
4 | var map = [];
5 | var timestamp = [];
6 | var delay = [];
7 | var doppler = [];
8 | var detection = '';
9 | var ts = '';
10 | var output = [];
11 | const options_timestamp = {
12 | host: '127.0.0.1',
13 | path: '/api/timestamp',
14 | port: 3000
15 | };
16 | const options_detection = {
17 | host: '127.0.0.1',
18 | path: '/api/detection',
19 | port: 3000
20 | };
21 |
22 | function update_data() {
23 |
24 | // check if timestamp is updated
25 | http.get(options_timestamp, function(res) {
26 | res.setEncoding('utf8');
27 | res.on('data', function (body) {
28 | if (ts != body)
29 | {
30 | ts = body;
31 | http.get(options_detection, function(res) {
32 | let body_map = '';
33 | res.setEncoding('utf8');
34 | res.on('data', (chunk) => {
35 | body_map += chunk;
36 | });
37 | res.on('end', () => {
38 | try {
39 | detection = JSON.parse(body_map);
40 | map.push(detection);
41 | for (i = 0; i < map.length; i++)
42 | {
43 | if ((ts - map[i].timestamp)/1000 > time)
44 | {
45 | map.shift();
46 | }
47 | else
48 | {
49 | break;
50 | }
51 | }
52 | delay = [];
53 | doppler = [];
54 | timestamp = [];
55 | snr = [];
56 | for (var i = 0; i < map.length; i++)
57 | {
58 | for (var j = 0; j < map[i].delay.length; j++)
59 | {
60 | delay.push(map[i].delay[j]);
61 | doppler.push(map[i].doppler[j]);
62 | snr.push(map[i].snr[j]);
63 | timestamp.push(map[i].timestamp);
64 | }
65 | }
66 | output = {
67 | timestamp: timestamp,
68 | delay: delay,
69 | doppler: doppler,
70 | snr: snr
71 | };
72 | } catch (e) {
73 | console.error(e.message);
74 | }
75 | });
76 | });
77 | }
78 | });
79 | });
80 |
81 | };
82 |
83 | setInterval(update_data, 100);
84 |
85 | function get_data() {
86 | return output;
87 | };
88 |
89 | module.exports.get_data_detection = get_data;
--------------------------------------------------------------------------------
/src/capture/Capture.h:
--------------------------------------------------------------------------------
1 | /// @file Capture.h
2 | /// @class Capture
3 | /// @brief A class for a generic IQ capture device.
4 | /// @author 30hours
5 |
6 | #ifndef CAPTURE_H
7 | #define CAPTURE_H
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include // optional header, provided for std:: interop
14 | #include // needed for the examples below
15 |
16 | #include "data/IqData.h"
17 | #include "capture/Source.h"
18 |
19 | class Capture
20 | {
21 | private:
22 | /// @brief The valid capture devices.
23 | static const std::string VALID_TYPE[4];
24 |
25 | /// @brief The capture device type.
26 | std::string type;
27 |
28 | /// @brief True if IQ data to be saved.
29 | bool saveIq;
30 |
31 | /// @brief True if file replay is enabled.
32 | bool replay;
33 |
34 | /// @brief True if replay file should loop when complete.
35 | bool loop;
36 |
37 | /// @brief Absolute path of file to replay.
38 | std::string file;
39 |
40 | public:
41 |
42 | /// @brief Sampling frequency (Hz).
43 | uint32_t fs;
44 |
45 | /// @brief Center frequency (Hz).
46 | uint32_t fc;
47 |
48 | /// @brief Absolute path to IQ save location.
49 | std::string path;
50 |
51 | /// @brief Pointer to capture device.
52 | std::unique_ptr device;
53 |
54 | /// @brief Constructor.
55 | /// @param type The capture device type.
56 | /// @param fs Sampling frequency (Hz).
57 | /// @param fc Center frequency (Hz).
58 | /// @param path Absolute path to IQ save location.
59 | /// @return The object.
60 | Capture(std::string type, uint32_t fs, uint32_t fc, std::string path);
61 |
62 | /// @brief Implement the capture process.
63 | /// @param buffer1 Buffer for reference samples.
64 | /// @param buffer2 Buffer for surveillance samples.
65 | /// @param config Yaml config for device.
66 | /// @param ip_capture IP address of capture API.
67 | /// @param port_capture Port of capture API.
68 | /// @return Void.
69 | void process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config,
70 | std::string ip_capture, uint16_t port_capture);
71 |
72 | std::unique_ptr factory_source(const std::string& type,
73 | c4::yml::NodeRef config);
74 |
75 | /// @brief Set parameters to enable file replay.
76 | /// @param loop True if replay file should loop when complete.
77 | /// @param file Absolute path of file to replay.
78 | /// @return Void.
79 | void set_replay(bool loop, std::string file);
80 |
81 | };
82 |
83 | #endif
--------------------------------------------------------------------------------
/src/capture/Source.h:
--------------------------------------------------------------------------------
1 | /// @file Source.h
2 | /// @class Source
3 | /// @brief An abstract class for capture sources.
4 | /// @author 30hours
5 |
6 | #ifndef SOURCE_H
7 | #define SOURCE_H
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include "data/IqData.h"
14 |
15 | class Source
16 | {
17 | protected:
18 |
19 | /// @brief The capture device type.
20 | std::string type;
21 |
22 | /// @brief Center frequency (Hz).
23 | uint32_t fc;
24 |
25 | /// @brief Sampling frequency (Hz).
26 | uint32_t fs;
27 |
28 | /// @brief Absolute path to IQ save location.
29 | std::string path;
30 |
31 | /// @brief True if IQ data to be saved.
32 | bool *saveIq;
33 |
34 | /// @brief File stream to save IQ data.
35 | std::ofstream saveIqFile;
36 |
37 | public:
38 |
39 | Source();
40 |
41 | /// @brief Constructor.
42 | /// @param type The capture device type.
43 | /// @param fs Sampling frequency (Hz).
44 | /// @param fc Center frequency (Hz).
45 | /// @param path Absolute path to IQ save location.
46 | /// @return The object.
47 | Source(std::string type, uint32_t fc, uint32_t fs,
48 | std::string path, bool *saveIq);
49 |
50 | /// @brief Implement the capture process.
51 | /// @param buffer1 Buffer for reference samples.
52 | /// @param buffer2 Buffer for surveillance samples.
53 | /// @return Void.
54 | virtual void process(IqData *buffer1, IqData *buffer2) = 0;
55 |
56 | /// @brief Call methods to start capture.
57 | /// @return Void.
58 | virtual void start() = 0;
59 |
60 | /// @brief Call methods to gracefully stop capture.
61 | /// @return Void.
62 | virtual void stop() = 0;
63 |
64 | /// @brief Implement replay function on RSPduo.
65 | /// @param buffer1 Pointer to reference buffer.
66 | /// @param buffer2 Pointer to surveillance buffer.
67 | /// @param file Path to file to replay data from.
68 | /// @param loop True if samples should loop at EOF.
69 | /// @return Void.
70 | virtual void replay(IqData *buffer1, IqData *buffer2,
71 | std::string file, bool loop) = 0;
72 |
73 | /// @brief Open a new file to record IQ.
74 | /// @details First creates a new file from current timestamp.
75 | /// Files are of format ..iq.
76 | /// @return String of full path to file.
77 | std::string open_file();
78 |
79 | /// @brief Close IQ file gracefully.
80 | /// @return Void.
81 | void close_file();
82 |
83 | /// @brief Graceful handler for SIGTERM.
84 | /// @return Void.
85 | void kill();
86 |
87 | };
88 |
89 | #endif
--------------------------------------------------------------------------------
/src/process/clutter/WienerHopf.h:
--------------------------------------------------------------------------------
1 | /// @file WienerHopf.h
2 | /// @class WienerHopf
3 | /// @brief A class to implement a Wiener-Hopf clutter filter.
4 | /// @details Implements a Wiener-Hopf filter.
5 | /// Uses Cholesky decomposition to speed up matrix inversion, as the Toeplitz matrix is positive-definite and Hermitian.
6 | /// @author 30hours
7 | /// @todo Fix the segmentation fault from clutter filter numerical instability.
8 |
9 | #ifndef WIENERHOPF_H
10 | #define WIENERHOPF_H
11 |
12 | #include "data/IqData.h"
13 | #include
14 | #include
15 | #include
16 |
17 | class WienerHopf
18 | {
19 | private:
20 | /// @brief Minimum clutter filter delay (bins).
21 | int32_t delayMin;
22 |
23 | /// @brief Maximum clutter filter delay (bins).
24 | int32_t delayMax;
25 |
26 | /// @brief Number of bins (delayMax - delayMin + 1).
27 | uint32_t nBins;
28 |
29 | /// @brief Number of samples per CPI.
30 | uint32_t nSamples;
31 |
32 | /// @brief True if clutter filter processing is successful.
33 | bool success;
34 |
35 | /// @brief FFTW plans for clutter filter processing.
36 | /// @{
37 | fftw_plan fftX, fftY, fftA, fftB, fftFiltX, fftFiltW, fftFilt;
38 | /// @}
39 |
40 | /// @brief FFTW storage for clutter filter processing.
41 | /// @{
42 | std::complex *dataX, *dataY, *dataOutX, *dataOutY, *dataA, *dataB, *filtX, *filtW, *filt;
43 | /// @}
44 |
45 | /// @brief Deque storage for clutter filter processing.
46 | /// @{
47 | std::deque> xData, yData;
48 | /// @}
49 |
50 | /// @brief Autocorrelation toeplitz matrix.
51 | arma::cx_mat A;
52 |
53 | /// @brief Autocorrelation vector.
54 | arma::cx_vec a;
55 |
56 | /// @brief Cross-correlation vector.
57 | arma::cx_vec b;
58 |
59 | /// @brief Weights vector.
60 | arma::cx_vec w;
61 |
62 | public:
63 | /// @brief Constructor.
64 | /// @param delayMin Minimum clutter filter delay (bins).
65 | /// @param delayMax Maximum clutter filter delay (bins).
66 | /// @param nSamples Number of samples per CPI.
67 | /// @return The object.
68 | WienerHopf(int32_t delayMin, int32_t delayMax, uint32_t nSamples);
69 |
70 | /// @brief Destructor.
71 | /// @return Void.
72 | ~WienerHopf();
73 |
74 | /// @brief Implement the clutter filter.
75 | /// @param x Reference samples.
76 | /// @param y Surveillance samples.
77 | /// @return True if clutter filter successful.
78 | bool process(IqData *x, IqData *y);
79 | };
80 |
81 | #endif
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:22.04 as blah2_env
2 | LABEL maintainer="30hours "
3 | LABEL org.opencontainers.image.source https://github.com/30hours/blah2
4 |
5 | WORKDIR /blah2
6 | ADD lib lib
7 | RUN apt-get update && apt-get install -y software-properties-common \
8 | && apt-add-repository ppa:ettusresearch/uhd \
9 | && apt-get update \
10 | && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y \
11 | g++ make cmake git curl zip unzip doxygen graphviz \
12 | libfftw3-dev pkg-config gfortran libhackrf-dev \
13 | libuhd-dev=4.9.0.0-0ubuntu1~jammy2 \
14 | uhd-host=4.9.0.0-0ubuntu1~jammy2 \
15 | libusb-dev libusb-1.0.0-dev \
16 | && apt-get autoremove -y \
17 | && apt-get clean -y \
18 | && rm -rf /var/lib/apt/lists/*
19 |
20 | # install dependencies from vcpkg
21 | ENV VCPKG_ROOT=/opt/vcpkg
22 | RUN export PATH="/opt/vcpkg:${PATH}" \
23 | && git clone https://github.com/microsoft/vcpkg /opt/vcpkg \
24 | && if [ "$(uname -m)" = "aarch64" ]; then export VCPKG_FORCE_SYSTEM_BINARIES=1; fi \
25 | && /opt/vcpkg/bootstrap-vcpkg.sh -disableMetrics \
26 | && cd /blah2/lib && vcpkg integrate install \
27 | && vcpkg install --clean-after-build
28 |
29 | # install SDRplay API
30 | RUN export ARCH=$(uname -m) \
31 | && if [ "$ARCH" = "x86_64" ]; then \
32 | ARCH="amd64"; \
33 | fi \
34 | && export MAJVER="3.15" \
35 | && export MINVER="2" \
36 | && export VER=${MAJVER}.${MINVER} \
37 | && cd /blah2/lib/sdrplay-${VER} \
38 | && chmod +x SDRplay_RSP_API-Linux-${VER}.run \
39 | && ./SDRplay_RSP_API-Linux-${MAJVER}.${MINVER}.run --tar -xvf -C /blah2/lib/sdrplay-${VER} \
40 | && cp ${ARCH}/libsdrplay_api.so.${MAJVER} /usr/local/lib/libsdrplay_api.so \
41 | && cp ${ARCH}/libsdrplay_api.so.${MAJVER} /usr/local/lib/libsdrplay_api.so.${MAJVER} \
42 | && cp inc/* /usr/local/include \
43 | && chmod 644 /usr/local/lib/libsdrplay_api.so /usr/local/lib/libsdrplay_api.so.${MAJVER} \
44 | && ldconfig
45 |
46 | # install UHD API
47 | RUN uhd_images_downloader
48 |
49 | # install RTL-SDR API
50 | RUN git clone https://github.com/krakenrf/librtlsdr /opt/librtlsdr \
51 | && cd /opt/librtlsdr && mkdir build && cd build \
52 | && cmake ../ -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON && make && make install && ldconfig
53 |
54 | FROM blah2_env as blah2
55 | LABEL maintainer="30hours "
56 |
57 | ADD src src
58 | ADD test test
59 | ADD CMakeLists.txt CMakePresets.json Doxyfile /blah2/
60 | RUN mkdir -p build && cd build && cmake -S . --preset prod-release \
61 | -DCMAKE_PREFIX_PATH=$(echo /blah2/lib/vcpkg_installed/*/share) .. \
62 | && cd prod-release && make
63 | RUN chmod +x bin/blah2
64 |
--------------------------------------------------------------------------------
/src/capture/hackrf/HackRf.h:
--------------------------------------------------------------------------------
1 | /// @file HackRf.h
2 | /// @class HackRf
3 | /// @brief A class to capture data on the HackRF.
4 | /// @author sdn-ninja
5 | /// @author 30hours
6 | /// @todo Replay functionality.
7 |
8 | #ifndef HACKRF_H
9 | #define HACKRF_H
10 |
11 | #include "capture/Source.h"
12 | #include "data/IqData.h"
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | class HackRf : public Source
20 | {
21 | private:
22 |
23 | /// @brief Vector of serial numbers.
24 | /// @details Serial as given by hackrf_info.
25 | std::vector serial;
26 |
27 | /// @brief RX LNA (IF) gain, 0-40dB, 8dB steps.
28 | std::vector gainLna;
29 |
30 | /// @brief RX VGA (baseband) gain, 0-62dB, 2dB steps.
31 | std::vector gainVga;
32 |
33 | /// @brief Enable extra amplifier U13 on receive.
34 | std::vector ampEnable;
35 |
36 | /// @brief Check status of HackRF API returns.
37 | /// @param status Return code of API call.
38 | /// @param message Message if API call error.
39 | void check_status(uint8_t status, std::string message);
40 |
41 | protected:
42 | /// @brief Array of pointers to HackRF devices.
43 | hackrf_device* dev[2];
44 |
45 | /// @brief Callback function for HackRF samples.
46 | /// @param transfer HackRF transfer object.
47 | /// @return Void.
48 | static int rx_callback(hackrf_transfer* transfer);
49 |
50 | public:
51 |
52 | /// @brief Constructor.
53 | /// @param fc Center frequency (Hz).
54 | /// @param path Path to save IQ data.
55 | /// @return The object.
56 | HackRf(std::string type, uint32_t fc, uint32_t fs, std::string path,
57 | bool *saveIq, std::vector serial,
58 | std::vector gainLna, std::vector gainVga,
59 | std::vector ampEnable);
60 |
61 | /// @brief Implement capture function on HackRF.
62 | /// @param buffer1 Pointer to reference buffer.
63 | /// @param buffer2 Pointer to surveillance buffer.
64 | /// @return Void.
65 | void process(IqData *buffer1, IqData *buffer2);
66 |
67 | /// @brief Call methods to start capture.
68 | /// @return Void.
69 | void start();
70 |
71 | /// @brief Call methods to gracefully stop capture.
72 | /// @return Void.
73 | void stop();
74 |
75 | /// @brief Implement replay function on HackRF.
76 | /// @param buffer1 Pointer to reference buffer.
77 | /// @param buffer2 Pointer to surveillance buffer.
78 | /// @param file Path to file to replay data from.
79 | /// @param loop True if samples should loop at EOF.
80 | /// @return Void.
81 | void replay(IqData *buffer1, IqData *buffer2, std::string file, bool loop);
82 |
83 | };
84 |
85 | #endif
86 |
--------------------------------------------------------------------------------
/test/unit/process/tracker/TestTracker.cpp:
--------------------------------------------------------------------------------
1 | /// @file TestTracker.cpp
2 | /// @brief Unit test for Tracker.cpp
3 | /// @author 30hours
4 |
5 | #include
6 | #include
7 |
8 | #include "data/Detection.h"
9 | #include "data/Track.h"
10 | #include "process/tracker/Tracker.h"
11 | #include "data/meta/Constants.h"
12 |
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 | /// @brief Test constructor.
19 | /// @details Check constructor parameters created correctly.
20 | TEST_CASE("Constructor", "[constructor]")
21 | {
22 | uint32_t m = 3;
23 | uint32_t n = 5;
24 | uint32_t nDelete = 5;
25 | double cpi = 1;
26 | double maxAccInit = 10;
27 | double fs = 2000000;
28 | double rangeRes = (double)Constants::c/fs;
29 | double fc = 204640000;
30 | double lambda = (double)Constants::c/fc;
31 | Tracker tracker = Tracker(m, n, nDelete,
32 | cpi, maxAccInit, rangeRes, lambda);
33 | }
34 |
35 | /// @brief Test process for an ACTIVE track.
36 | TEST_CASE("Process ACTIVE track constant acc", "[process]")
37 | {
38 | uint32_t m = 3;
39 | uint32_t n = 5;
40 | uint32_t nDelete = 5;
41 | double cpi = 1;
42 | double maxAccInit = 10;
43 | double fs = 2000000;
44 | double rangeRes = (double)Constants::c/fs;
45 | double fc = 204640000;
46 | double lambda = (double)Constants::c/fc;
47 | Tracker tracker = Tracker(m, n, nDelete,
48 | cpi, maxAccInit, rangeRes, lambda);
49 |
50 |
51 | // create detections with constant acc 5 Hz/s
52 | std::vector timestamp = {0,1,2,3,4,5,6,7,8,9,10};
53 | std::vector delay = {10};
54 | std::vector doppler = {-20,-15,-10,-5,0,5,10,15,20,25};
55 |
56 | std::string state = "ACTIVE";
57 | }
58 |
59 | /// @brief Test predict for kinematics equations.
60 | TEST_CASE("Test predict", "[predict]")
61 | {
62 | uint32_t m = 3;
63 | uint32_t n = 5;
64 | uint32_t nDelete = 5;
65 | double cpi = 1;
66 | double maxAccInit = 10;
67 | double fs = 2000000;
68 | double rangeRes = (double)Constants::c/fs;
69 | double fc = 204640000;
70 | double lambda = (double)Constants::c/fc;
71 | Tracker tracker = Tracker(m, n, nDelete,
72 | cpi, maxAccInit, rangeRes, lambda);
73 |
74 | Detection input = Detection(10, -20, 0);
75 | double acc = 5;
76 | double T = 1;
77 | Detection prediction = tracker.predict(input, acc, T);
78 | Detection prediction_truth = Detection(9.821, -15, 0);
79 |
80 | CHECK_THAT(prediction.get_delay().front(),
81 | Catch::Matchers::WithinAbs(prediction_truth.get_delay().front(), 0.01));
82 | CHECK_THAT(prediction.get_doppler().front(),
83 | Catch::Matchers::WithinAbs(prediction_truth.get_doppler().front(), 0.01));
84 | }
--------------------------------------------------------------------------------
/src/data/IqData.h:
--------------------------------------------------------------------------------
1 | /// @file IqData.h
2 | /// @class IqData
3 | /// @brief A class to store IQ data.
4 | /// @details Implements a FIFO queue to store IQ samples.
5 | /// @author 30hours
6 |
7 | #ifndef IQDATA_H
8 | #define IQDATA_H
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | class IqData
17 | {
18 | private:
19 | /// @brief Maximum number of samples.
20 | uint32_t n;
21 |
22 | /// @brief True if should not push to buffer (mutex).
23 | std::mutex mutex_lock;
24 |
25 | /// @brief Pointer to IQ data.
26 | std::deque> *data;
27 |
28 | /// @brief Minimum value.
29 | double min;
30 |
31 | /// @brief Maximum value.
32 | double max;
33 |
34 | /// @brief Mean value.
35 | double mean;
36 |
37 | /// @brief Spectrum vector.
38 | std::vector> spectrum;
39 |
40 | /// @brief Frequency vector (Hz).
41 | std::vector frequency;
42 |
43 | public:
44 | /// @brief Constructor.
45 | /// @param n Number of samples.
46 | /// @return The object.
47 | IqData(uint32_t n);
48 |
49 | /// @brief Getter for maximum number of samples.
50 | /// @return Maximum number of samples.
51 | uint32_t get_n();
52 |
53 | /// @brief Getter for current data length.
54 | /// @return Number of samples currently in data.
55 | uint32_t get_length();
56 |
57 | /// @brief Locker for mutex.
58 | /// @return Void.
59 | void lock();
60 |
61 | /// @brief Unlocker for mutex.
62 | /// @return Void.
63 | void unlock();
64 |
65 | /// @brief Getter for data.
66 | /// @return IQ data.
67 | std::deque> get_data();
68 |
69 | /// @brief Push a sample to the queue.
70 | /// @param sample A single sample.
71 | /// @return Void.
72 | void push_back(std::complex sample);
73 |
74 | /// @brief Pop the front of the queue.
75 | /// @return Sample from the front of the queue.
76 | std::complex pop_front();
77 |
78 | /// @brief Print to stdout (debug).
79 | /// @return Void.
80 | void print();
81 |
82 | /// @brief Clear samples from the queue.
83 | /// @return Void.
84 | void clear();
85 |
86 | /// @brief Update the time differences and names.
87 | /// @param spectrum Spectrum vector.
88 | /// @return Void.
89 | void update_spectrum(std::vector> spectrum);
90 |
91 | /// @brief Update the time differences and names.
92 | /// @param frequency Frequency vector.
93 | /// @return Void.
94 | void update_frequency(std::vector frequency);
95 |
96 | /// @brief Generate JSON of the signal and metadata.
97 | /// @param timestamp Current time (POSIX ms).
98 | /// @return JSON string.
99 | std::string to_json(uint64_t timestamp);
100 | };
101 |
102 | #endif
--------------------------------------------------------------------------------
/src/data/meta/Timing.cpp:
--------------------------------------------------------------------------------
1 | #include "Timing.h"
2 | #include
3 | #include
4 |
5 | #include "rapidjson/document.h"
6 | #include "rapidjson/writer.h"
7 | #include "rapidjson/stringbuffer.h"
8 | #include "rapidjson/filewritestream.h"
9 |
10 | // constructor
11 | Timing::Timing(uint64_t _tStart)
12 | {
13 | tStart = _tStart;
14 | n = 0;
15 | }
16 |
17 | void Timing::update(uint64_t _tNow, std::vector _time, std::vector _name)
18 | {
19 | n = n + 1;
20 | tNow = _tNow;
21 | time = _time;
22 | name = _name;
23 | uptime = _tNow-tStart;
24 | }
25 |
26 | std::string Timing::to_json()
27 | {
28 | rapidjson::Document document;
29 | document.SetObject();
30 | rapidjson::Document::AllocatorType &allocator = document.GetAllocator();
31 |
32 | document.AddMember("timestamp", tNow, allocator);
33 | document.AddMember("nCpi", n, allocator);
34 | document.AddMember("uptime_s", uptime/1000.0, allocator);
35 | document.AddMember("uptime_days", uptime/1000.0/60/60/24, allocator);
36 | rapidjson::Value name_value;
37 | for (size_t i = 0; i < time.size(); i++)
38 | {
39 | name_value = rapidjson::StringRef(name[i].c_str());
40 | document.AddMember(name_value, time[i], allocator);
41 | }
42 |
43 | rapidjson::StringBuffer strbuf;
44 | rapidjson::Writer writer(strbuf);
45 | writer.SetMaxDecimalPlaces(2);
46 | document.Accept(writer);
47 |
48 | return strbuf.GetString();
49 | }
50 |
51 | bool Timing::save(std::string _json, std::string filename)
52 | {
53 | using namespace rapidjson;
54 |
55 | rapidjson::Document document;
56 |
57 | // create file if it doesn't exist
58 | if (FILE *fp = fopen(filename.c_str(), "r"); !fp)
59 | {
60 | if (fp = fopen(filename.c_str(), "w"); !fp)
61 | return false;
62 | fputs("[]", fp);
63 | fclose(fp);
64 | }
65 |
66 | // add the document to the file
67 | if (FILE *fp = fopen(filename.c_str(), "rb+"); fp)
68 | {
69 | // check if first is [
70 | std::fseek(fp, 0, SEEK_SET);
71 | if (getc(fp) != '[')
72 | {
73 | std::fclose(fp);
74 | return false;
75 | }
76 |
77 | // is array empty?
78 | bool isEmpty = false;
79 | if (getc(fp) == ']')
80 | isEmpty = true;
81 |
82 | // check if last is ]
83 | std::fseek(fp, -1, SEEK_END);
84 | if (getc(fp) != ']')
85 | {
86 | std::fclose(fp);
87 | return false;
88 | }
89 |
90 | // replace ] by ,
91 | fseek(fp, -1, SEEK_END);
92 | if (!isEmpty)
93 | fputc(',', fp);
94 |
95 | // add json element
96 | fwrite(_json.c_str(), sizeof(char), _json.length(), fp);
97 |
98 | // close the array
99 | std::fputc(']', fp);
100 | fclose(fp);
101 | return true;
102 | }
103 | return false;
104 | }
105 |
--------------------------------------------------------------------------------
/src/process/detection/CfarDetector1D.cpp:
--------------------------------------------------------------------------------
1 | #include "CfarDetector1D.h"
2 | #include "data/Map.h"
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | // constructor
9 | CfarDetector1D::CfarDetector1D(double _pfa, int8_t _nGuard, int8_t _nTrain, int8_t _minDelay, double _minDoppler)
10 | {
11 | // input
12 | pfa = _pfa;
13 | nGuard = _nGuard;
14 | nTrain = _nTrain;
15 | minDelay = _minDelay;
16 | minDoppler = _minDoppler;
17 | }
18 |
19 | CfarDetector1D::~CfarDetector1D()
20 | {
21 | }
22 |
23 | std::unique_ptr CfarDetector1D::process(Map> *x)
24 | {
25 | int32_t nDelayBins = x->get_nCols();
26 | int32_t nDopplerBins = x->get_nRows();
27 |
28 | std::vector> mapRow;
29 | std::vector mapRowSquare, mapRowSnr;
30 |
31 | // store detections temporarily
32 | std::vector delay;
33 | std::vector doppler;
34 | std::vector snr;
35 |
36 | // loop over every cell
37 | for (int i = 0; i < nDopplerBins; i++)
38 | {
39 | // skip if less than min Doppler
40 | if (std::abs(x->doppler[i]) < minDoppler)
41 | {
42 | continue;
43 | }
44 | mapRow = x->get_row(i);
45 | for (int j = 0; j < nDelayBins; j++)
46 | {
47 | mapRowSquare.push_back((double) std::abs(mapRow[j]*mapRow[j]));
48 | mapRowSnr.push_back((double)10 * std::log10(std::abs(mapRow[j])) - x->noisePower);
49 | }
50 | for (int j = 0; j < nDelayBins; j++)
51 | {
52 | // skip if less than min delay
53 | if (x->delay[j] < minDelay)
54 | {
55 | continue;
56 | }
57 | // get train cell indices
58 | std::vector iTrain;
59 | for (int k = j-nGuard-nTrain; k < j-nGuard; k++)
60 | {
61 | if (k > 0 && k < nDelayBins)
62 | {
63 | iTrain.push_back(k);
64 | }
65 | }
66 | for (int k = j+nGuard+1; k < j+nGuard+nTrain+1; k++)
67 | {
68 | if (k >= 0 && k < nDelayBins)
69 | {
70 | iTrain.push_back(k);
71 | }
72 | }
73 |
74 | // compute threshold
75 | int nCells = iTrain.size();
76 | double alpha = nCells * (pow(pfa, -1.0 / nCells) - 1);
77 | double trainNoise = 0.0;
78 | for (int k = 0; k < nCells; k++)
79 | {
80 | trainNoise += mapRowSquare[iTrain[k]];
81 | }
82 | trainNoise /= nCells;
83 | double threshold = alpha * trainNoise;
84 |
85 | // detection if over threshold
86 | if (mapRowSquare[j] > threshold)
87 | {
88 | delay.push_back(j + x->delay[0]);
89 | doppler.push_back(x->doppler[i]);
90 | snr.push_back(mapRowSnr[j]);
91 | }
92 | iTrain.clear();
93 | }
94 | mapRowSquare.clear();
95 | mapRowSnr.clear();
96 | }
97 |
98 | // create detection
99 | return std::make_unique(delay, doppler, snr);
100 | }
101 |
--------------------------------------------------------------------------------
/src/capture/kraken/Kraken.h:
--------------------------------------------------------------------------------
1 | /// @file Kraken.h
2 | /// @class Kraken
3 | /// @brief A class to capture data on the Kraken SDR.
4 | /// @details Uses a custom librtlsdr API to extract samples.
5 | /// Uses 2 channels of the Kraken to capture IQ data.
6 | /// The noise source phase synchronisation is not required for 2 channel operation.
7 | /// Future work is to replicate the Heimdall DAQ phase syncronisation.
8 | /// This will enable a surveillance array of up to 4 antenna elements.
9 | /// Requires a custom librtlsdr which includes method rtlsdr_set_dithering().
10 | /// The original steve-m/librtlsdr does not include this method.
11 | /// This is included in librtlsdr/librtlsdr or krakenrf/librtlsdr.
12 | /// Also works using 2 RTL-SDRs which have been clock synchronised.
13 | /// @author 30hours, Michael Brock, sdn-ninja
14 | /// @todo Add support for multiple surveillance channels.
15 | /// @todo Replay support.
16 |
17 | #ifndef KRAKEN_H
18 | #define KRAKEN_H
19 |
20 | #include "capture/Source.h"
21 | #include "data/IqData.h"
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | class Kraken : public Source
29 | {
30 | private:
31 |
32 | /// @brief Individual RTL-SDR devices.
33 | rtlsdr_dev_t* devs[5];
34 |
35 | /// @brief Device indices for Kraken.
36 | std::vector channelIndex;
37 |
38 | /// @brief Gain for each channel.
39 | std::vector gain;
40 |
41 | /// @brief Check status of API returns.
42 | /// @param status Return code of API call.
43 | /// @param message Message if API call error.
44 | /// @return Void.
45 | void check_status(int status, std::string message);
46 |
47 | /// @brief Callback function when buffer is filled.
48 | /// @param buf Pointer to buffer of IQ data.
49 | /// @param len Length of buffer.
50 | /// @param ctx Context data for callback.
51 | /// @return Void.
52 | static void callback(unsigned char *buf, uint32_t len, void *ctx);
53 |
54 | public:
55 |
56 | /// @brief Constructor.
57 | /// @param fc Center frequency (Hz).
58 | /// @param path Path to save IQ data.
59 | /// @return The object.
60 | Kraken(std::string type, uint32_t fc, uint32_t fs, std::string path,
61 | bool *saveIq, std::vector gain);
62 |
63 | /// @brief Implement capture function on KrakenSDR.
64 | /// @param buffer Pointers to buffers for each channel.
65 | /// @return Void.
66 | void process(IqData *buffer1, IqData *buffer2);
67 |
68 | /// @brief Call methods to start capture.
69 | /// @return Void.
70 | void start();
71 |
72 | /// @brief Call methods to gracefully stop capture.
73 | /// @return Void.
74 | void stop();
75 |
76 | /// @brief Implement replay function on the Kraken.
77 | /// @param buffers Pointers to buffers for each channel.
78 | /// @param file Path to file to replay data from.
79 | /// @param loop True if samples should loop at EOF.
80 | /// @return Void.
81 | void replay(IqData *buffer1, IqData *buffer2, std::string file, bool loop);
82 |
83 | };
84 |
85 | #endif
86 |
--------------------------------------------------------------------------------
/src/data/IqData.cpp:
--------------------------------------------------------------------------------
1 | #include "IqData.h"
2 | #include
3 | #include
4 |
5 | #include "rapidjson/document.h"
6 | #include "rapidjson/writer.h"
7 | #include "rapidjson/stringbuffer.h"
8 | #include "rapidjson/filewritestream.h"
9 |
10 | // constructor
11 | IqData::IqData(uint32_t _n)
12 | {
13 | n = _n;
14 | data = new std::deque>;
15 | }
16 |
17 | uint32_t IqData::get_n()
18 | {
19 | return n;
20 | }
21 |
22 | uint32_t IqData::get_length()
23 | {
24 | return data->size();
25 | }
26 |
27 | void IqData::lock()
28 | {
29 | mutex_lock.lock();
30 | }
31 |
32 | void IqData::unlock()
33 | {
34 | mutex_lock.unlock();
35 | }
36 |
37 | std::deque> IqData::get_data()
38 | {
39 | return *data;
40 | }
41 |
42 | void IqData::push_back(std::complex sample)
43 | {
44 | if (data->size() < n)
45 | {
46 | data->push_back(sample);
47 | }
48 | else
49 | {
50 | data->pop_front();
51 | data->push_back(sample);
52 | }
53 | }
54 |
55 | std::complex IqData::pop_front()
56 | {
57 | if (data->empty()) {
58 | throw std::runtime_error("Attempting to pop from an empty deque");
59 | }
60 | std::complex sample = data->front();
61 | data->pop_front();
62 | return sample;
63 | }
64 | void IqData::print()
65 | {
66 | int n = data->size();
67 | std::cout << data->size() << std::endl;
68 | for (int i = 0; i < n; i++)
69 | {
70 | std::cout << data->front() << std::endl;
71 | data->pop_front();
72 | }
73 | }
74 |
75 | void IqData::clear()
76 | {
77 | while (!data->empty())
78 | {
79 | data->pop_front();
80 | }
81 | }
82 |
83 | void IqData::update_spectrum(std::vector> _spectrum)
84 | {
85 | spectrum = _spectrum;
86 | }
87 |
88 | void IqData::update_frequency(std::vector _frequency)
89 | {
90 | frequency = _frequency;
91 | }
92 |
93 | std::string IqData::to_json(uint64_t timestamp)
94 | {
95 | rapidjson::Document document;
96 | document.SetObject();
97 | rapidjson::Document::AllocatorType &allocator = document.GetAllocator();
98 |
99 | // store frequency array
100 | rapidjson::Value arrayFrequency(rapidjson::kArrayType);
101 | for (size_t i = 0; i < frequency.size(); i++)
102 | {
103 | arrayFrequency.PushBack(frequency[i], allocator);
104 | }
105 |
106 | // store spectrum array
107 | rapidjson::Value arraySpectrum(rapidjson::kArrayType);
108 | for (size_t i = 0; i < spectrum.size(); i++)
109 | {
110 | arraySpectrum.PushBack(10 * std::log10(std::abs(spectrum[i])), allocator);
111 | }
112 |
113 | document.AddMember("timestamp", timestamp, allocator);
114 | document.AddMember("min", min, allocator);
115 | document.AddMember("max", max, allocator);
116 | document.AddMember("mean", mean, allocator);
117 | document.AddMember("frequency", arrayFrequency, allocator);
118 | document.AddMember("spectrum", arraySpectrum, allocator);
119 |
120 | rapidjson::StringBuffer strbuf;
121 | rapidjson::Writer writer(strbuf);
122 | writer.SetMaxDecimalPlaces(2);
123 | document.Accept(writer);
124 |
125 | return strbuf.GetString();
126 | }
--------------------------------------------------------------------------------
/src/process/tracker/Tracker.h:
--------------------------------------------------------------------------------
1 | /// @file Tracker.h
2 | /// @class Tracker
3 | /// @brief A class to implement a bistatic tracker.
4 | /// @details Key functions are update, initiate, smooth and remove.
5 | /// @details Update before initiate to avoid duplicate tracks.
6 | /// @author 30hours
7 | /// @todo Add smoothing capability.
8 | /// @todo Fix units up.
9 | /// @todo I don't think I callback the true CPI time from ambiguity.
10 |
11 | #ifndef TRACKER_H
12 | #define TRACKER_H
13 |
14 | #include "data/Detection.h"
15 | #include "data/Track.h"
16 |
17 | #include
18 | #include
19 |
20 | class Tracker
21 | {
22 | private:
23 | /// @brief Track initiation constant for M of N detections.
24 | uint32_t m;
25 |
26 | /// @brief Track initiation constant for M of N detections.
27 | uint32_t n;
28 |
29 | /// @brief Number of missed predictions to delete a tentative track.
30 | uint32_t nDelete;
31 |
32 | /// @brief True CPI time for acceleration resolution(s).
33 | double cpi;
34 |
35 | /// @brief Maximum acceleration to initiate track (Hz/s).
36 | double maxAccInit;
37 |
38 | /// @brief Range resolution for kinematics equations (m).
39 | double rangeRes;
40 |
41 | /// @brief Wavelength for kinematics equations (m).
42 | double lambda;
43 |
44 | /// @brief Acceleration values to initiate track (Hz/s).
45 | std::vector accInit;
46 |
47 | /// @brief Index of detections already updated.
48 | std::vector doNotInitiate;
49 |
50 | /// @brief POSIX timestamp of last update (ms).
51 | uint64_t timestamp;
52 |
53 | /// @brief Track data.
54 | Track track;
55 |
56 | public:
57 | /// @brief Constructor.
58 | /// @param m Track initiation constant for M of N detections.
59 | /// @param n Track initiation constant for M of N detections.
60 | /// @param nDelete Number of missed predictions to delete a tentative track.
61 | /// @param cpi True CPI time for acceleration resolution(s).
62 | /// @param maxAccInit Maximum acceleration to initiate track (Hz/s).
63 | /// @param rangeRes Range resolution for kinematics equations (m).
64 | /// @param lambda Wavelength for kinematics equations (m).
65 | /// @return The object.
66 | Tracker(uint32_t m, uint32_t n, uint32_t nDelete, double cpi,
67 | double maxAccInit, double rangeRes, double lambda);
68 |
69 | /// @brief Destructor.
70 | /// @return Void.
71 | ~Tracker();
72 |
73 | /// @brief Run through key functions of tracker.
74 | /// @param detection Detection data for last CPI.
75 | /// @param timestamp POSIX timestamp (ms).
76 | /// @return Pointer to track data.
77 | std::unique_ptr