diff --git a/CMakeLists.txt b/CMakeLists.txt index 1482a5f..a335d23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ add_executable(blah2 src/capture/rspduo/RspDuo.cpp src/capture/usrp/Usrp.cpp src/capture/hackrf/HackRf.cpp + src/capture/kraken/Kraken.cpp src/process/ambiguity/Ambiguity.cpp src/process/clutter/WienerHopf.cpp src/process/detection/CfarDetector1D.cpp @@ -71,6 +72,7 @@ target_link_libraries(blah2 PRIVATE fftw3_threads sdrplay hackrf + rtlsdr ) target_include_directories(blah2 PRIVATE RAPIDJSON_INCLUDE_DIRS "rapidjson/allocators.h") diff --git a/Dockerfile b/Dockerfile index 7f6b2c3..e29cfcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y software-properties-common \ libfftw3-dev pkg-config gfortran libhackrf-dev \ libuhd-dev=4.6.0.0-0ubuntu1~jammy1 \ uhd-host=4.6.0.0-0ubuntu1~jammy1 \ + libusb-dev libusb-1.0.0-dev \ && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* @@ -35,6 +36,11 @@ RUN chmod +x /blah2/lib/sdrplay-3.14.0/SDRplay_RSP_API-Linux-3.14.0.run \ # install UHD API RUN uhd_images_downloader +# install RTL-SDR API +RUN git clone https://github.com/krakenrf/librtlsdr /opt/librtlsdr \ + && cd /opt/librtlsdr && mkdir build && cd build \ + && cmake ../ -DINSTALL_UDEV_RULES=ON && make && make install && ldconfig + FROM blah2_env as blah2 LABEL maintainer="30hours " diff --git a/config/config-kraken.yml b/config/config-kraken.yml new file mode 100644 index 0000000..aed4480 --- /dev/null +++ b/config/config-kraken.yml @@ -0,0 +1,84 @@ +capture: + fs: 2000000 + fc: 204640000 + device: + type: "Kraken" + gain: [15.0, 15.0] + array: + x: [0, 0] + y: [0, 0] + z: [0, 0] + boresight: 0.0 + replay: + state: false + loop: true + file: '/opt/blah2/replay/file.kraken' + +process: + data: + cpi: 0.5 + buffer: 1.5 + overlap: 0 + ambiguity: + delayMin: -10 + delayMax: 400 + dopplerMin: -200 + dopplerMax: 200 + clutter: + delayMin: -10 + delayMax: 400 + detection: + pfa: 0.00001 + nGuard: 2 + nTrain: 6 + minDelay: 5 + minDoppler: 15 + nCentroid: 6 + tracker: + initiate: + M: 3 + N: 5 + maxAcc: 10 + delete: 10 + smooth: "none" + +network: + ip: 0.0.0.0 + ports: + api: 3000 + map: 3001 + detection: 3002 + track: 3003 + timestamp: 4000 + timing: 4001 + iqdata: 4002 + config: 4003 + +truth: + adsb: + enabled: false + tar1090: 'adsb.30hours.dev' + adsb2dd: 'adsb2dd.30hours.dev' + ais: + enabled: false + ip: 0.0.0.0 + port: 30001 + +location: + rx: + latitude: -34.9286 + longitude: 138.5999 + altitude: 50 + name: "Adelaide" + tx: + latitude: -34.9810 + longitude: 138.7081 + altitude: 750 + name: "Mount Lofty" + +save: + iq: true + map: false + detection: false + timing: false + path: "/blah2/save/" diff --git a/src/capture/Capture.cpp b/src/capture/Capture.cpp index 73511a8..b734528 100644 --- a/src/capture/Capture.cpp +++ b/src/capture/Capture.cpp @@ -2,12 +2,13 @@ #include "rspduo/RspDuo.h" #include "usrp/Usrp.h" #include "hackrf/HackRf.h" +#include "kraken/Kraken.h" #include #include #include // constants -const std::string Capture::VALID_TYPE[3] = {"RspDuo", "Usrp", "HackRF"}; +const std::string Capture::VALID_TYPE[4] = {"RspDuo", "Usrp", "HackRF", "Kraken"}; // constructor Capture::Capture(std::string _type, uint32_t _fs, uint32_t _fc, std::string _path) @@ -66,10 +67,12 @@ void Capture::process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config, std::unique_ptr Capture::factory_source(const std::string& type, c4::yml::NodeRef config) { + // SDRplay RSPduo if (type == VALID_TYPE[0]) { return std::make_unique(type, fc, fs, path, &saveIq); } + // Usrp else if (type == VALID_TYPE[1]) { std::string address, subdev; @@ -87,10 +90,10 @@ std::unique_ptr Capture::factory_source(const std::string& type, c4::yml 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); } + // HackRF else if (type == VALID_TYPE[2]) { std::vector serial; @@ -120,11 +123,22 @@ std::unique_ptr Capture::factory_source(const std::string& type, c4::yml ampEnable.push_back(_ampEnable); config["amp_enable"][1] >> _ampEnable; ampEnable.push_back(_ampEnable); - return std::make_unique(type, fc, fs, path, &saveIq, serial, gainLna, gainVga, ampEnable); } - // Handle unknown type + // Kraken + else if (type == VALID_TYPE[3]) + { + std::vector gain; + float _gain; + for (auto child : config["gain"].children()) + { + c4::atof(child.val(), &_gain); + gain.push_back(static_cast(_gain)); + } + return std::make_unique(type, fc, fs, path, &saveIq, gain); + } + // handle unknown type std::cerr << "Error: Source type does not exist." << std::endl; return nullptr; } diff --git a/src/capture/Capture.h b/src/capture/Capture.h index 1e441a7..9994d4b 100644 --- a/src/capture/Capture.h +++ b/src/capture/Capture.h @@ -7,6 +7,7 @@ #define CAPTURE_H #include +#include #include #include #include // optional header, provided for std:: interop @@ -19,7 +20,7 @@ class Capture { private: /// @brief The valid capture devices. - static const std::string VALID_TYPE[3]; + static const std::string VALID_TYPE[4]; /// @brief The capture device type. std::string type; diff --git a/src/capture/kraken/Kraken.cpp b/src/capture/kraken/Kraken.cpp new file mode 100644 index 0000000..b85b971 --- /dev/null +++ b/src/capture/kraken/Kraken.cpp @@ -0,0 +1,116 @@ +#include "Kraken.h" + +#include +#include +#include +#include + +// constructor +Kraken::Kraken(std::string _type, uint32_t _fc, uint32_t _fs, + std::string _path, bool *_saveIq, std::vector _gain) + : Source(_type, _fc, _fs, _path, _saveIq) +{ + // convert gain to tenths of dB + for (int i = 0; i <= _gain.size(); i++) + { + gain.push_back(static_cast(_gain[i]*10)); + channelIndex.push_back(i); + } + std::vector devs(channelIndex.size()); + + // store all valid gains + std::vector validGains; + int nGains; + nGains = rtlsdr_get_tuner_gains(devs[0], nullptr); + check_status(nGains, "Failed to get number of gains."); + std::unique_ptr _validGains(new int[nGains]); + int status = rtlsdr_get_tuner_gains(devs[0], _validGains.get()); + check_status(status, "Failed to get number of gains."); + validGains.assign(_validGains.get(), _validGains.get() + nGains); + + // update gains to next value if invalid + for (int i = 0; i <= _gain.size(); i++) + { + int adjustedGain = static_cast(_gain[i] * 10); + auto it = std::lower_bound(validGains.begin(), + validGains.end(), adjustedGain); + if (it != validGains.end()) { + gain.push_back(*it); + } else { + gain.push_back(validGains.back()); + } + std::cout << "[Kraken] Gain update on channel " << i << " from " << + adjustedGain << " to " << gain[i] << "." << std::endl; + } +} + +void Kraken::start() +{ + int status; + for (size_t i = 0; i < channelIndex.size(); i++) + { + status = rtlsdr_open(&devs[i], channelIndex[i]); + check_status(status, "Failed to open device."); + status = rtlsdr_set_center_freq(devs[i], fc); + check_status(status, "Failed to set center frequency."); + status = rtlsdr_set_sample_rate(devs[i], fs); + check_status(status, "Failed to set sample rate."); + status = rtlsdr_set_dithering(devs[i], 0); // disable dither + check_status(status, "Failed to disable dithering."); + status = rtlsdr_set_tuner_gain_mode(devs[i], 1); // disable AGC + check_status(status, "Failed to disable AGC."); + status = rtlsdr_set_tuner_gain(devs[i], gain[i]); + check_status(status, "Failed to set gain."); + status = rtlsdr_reset_buffer(devs[i]); + check_status(status, "Failed to reset buffer."); + } +} + +void Kraken::stop() +{ + int status; + for (size_t i = 0; i < channelIndex.size(); i++) + { + status = rtlsdr_cancel_async(devs[i]); + check_status(status, "Failed to stop async read."); + } +} + +void Kraken::process(IqData *buffer1, IqData *buffer2) +{ + std::vector threads; + for (size_t i = 0; i < channelIndex.size(); i++) + { + threads.emplace_back(rtlsdr_read_async, devs[i], callback, &channelIndex, 0, 16 * 16384); + } + + // join threads + for (auto& thread : threads) { + thread.join(); + } +} + +void Kraken::callback(unsigned char *buf, uint32_t len, void *ctx) +{ + int deviceIndex = *reinterpret_cast(ctx); + // buffers[i]->lock(); + // for (size_t j = 0; j < n_read; j++) + // { + // buffers[i]->push_back({buffer[j].real(), buffer[j].imag()}); + // } + // buffers[i]->unlock(); +} + +void Kraken::replay(IqData *buffer1, IqData *buffer2, std::string _file, bool _loop) +{ + // todo +} + +void Kraken::check_status(int status, std::string message) +{ + if (status < 0) + { + throw std::runtime_error("[Kraken] " + message); + } +} + diff --git a/src/capture/kraken/Kraken.h b/src/capture/kraken/Kraken.h new file mode 100644 index 0000000..872bac9 --- /dev/null +++ b/src/capture/kraken/Kraken.h @@ -0,0 +1,84 @@ +/// @file Kraken.h +/// @class Kraken +/// @brief A class to capture data on the Kraken SDR. +/// @details Uses a custom librtlsdr API to extract samples. +/// Uses 2 channels of the Kraken to capture IQ data. +/// The noise source phase synchronisation is not required for 2 channel operation. +/// Future work is to replicate the Heimdall DAQ phase syncronisation. +/// This will enable a surveillance array of up to 4 antenna elements. +/// Requires a custom librtlsdr which includes method rtlsdr_set_dithering(). +/// The original steve-m/librtlsdr does not include this method. +/// This is included in librtlsdr/librtlsdr or krakenrf/librtlsdr. +/// @author 30hours +/// @todo Add support for multiple surveillance channels. +/// @todo Replay support. + +#ifndef KRAKEN_H +#define KRAKEN_H + +#include "capture/Source.h" +#include "data/IqData.h" + +#include +#include +#include +#include + +class Kraken : public Source +{ +private: + + /// @brief Individual RTL-SDR devices. + std::vector devs; + + /// @brief Device indices for Kraken. + std::vector channelIndex; + + /// @brief Gain for each channel. + std::vector gain; + + /// @brief Check status of API returns. + /// @param status Return code of API call. + /// @param message Message if API call error. + /// @return Void. + void check_status(int status, std::string message); + + /// @brief Callback function when buffer is filled. + /// @param buf Pointer to buffer of IQ data. + /// @param len Length of buffer. + /// @param ctx Context data for callback. + /// @return Void. + static void callback(unsigned char *buf, uint32_t len, void *ctx); + +public: + + /// @brief Constructor. + /// @param fc Center frequency (Hz). + /// @param path Path to save IQ data. + /// @return The object. + Kraken(std::string type, uint32_t fc, uint32_t fs, std::string path, + bool *saveIq, std::vector gain); + + /// @brief Implement capture function on KrakenSDR. + /// @param buffer Pointers to buffers for each channel. + /// @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 the Kraken. + /// @param buffers Pointers to buffers for each channel. + /// @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