diff --git a/README.md b/README.md index 95183e1c..7118af72 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,15 @@ Bundled dependencies: * libcorrect (currently a fork with CMake related fixes) * libaec +Ettus Research USRP dependencies (required when building USRP support) + +* libuhd +* libboost + ## Build ``` shell -git clone https://github.com/pietern/goestools +git clone --recursive https://github.com/codient/goestools cd goestools mkdir -p build cd build diff --git a/etc/goesrecv-uhd.conf b/etc/goesrecv-uhd.conf new file mode 100644 index 00000000..5f44bcb6 --- /dev/null +++ b/etc/goesrecv-uhd.conf @@ -0,0 +1,30 @@ +[demodulator] +mode = "hrit" +source = "uhd" + +[rtlsdr] +frequency = 1694100000 +sample_rate = 2400000 +gain = 5 +bias_tee = false + +[uhd] +type = "b200" +frequency = 1694100000 +sample_rate = 4000000 +gain = 32 + +[costas] +max_deviation = 200e3 + +[nanomsg] +sample_rate = 2400000 +connect = "tcp://0.0.0.0:5005" +receive_buffer = 2097152 + +[decoder.packet_publisher] +bind = "tcp://0.0.0.0:5004" +send_buffer = 1048576 + +[monitor] +statsd_address = "udp4://localhost:8125" diff --git a/src/goesproc/handler_goesr.cc b/src/goesproc/handler_goesr.cc index 7741f314..dfedca73 100644 --- a/src/goesproc/handler_goesr.cc +++ b/src/goesproc/handler_goesr.cc @@ -600,3 +600,4 @@ void GOESRImageHandler::overlayMaps(const GOESRProduct& product, cv::Mat& mat) { mat = drawer.draw(mat); #endif } + diff --git a/src/goesrecv/CMakeLists.txt b/src/goesrecv/CMakeLists.txt index b8a0934e..8956ebea 100644 --- a/src/goesrecv/CMakeLists.txt +++ b/src/goesrecv/CMakeLists.txt @@ -9,6 +9,14 @@ add_library(publisher ) target_link_libraries(publisher nanomsg) +pkg_check_modules(UHD uhd) +if(NOT UHD_FOUND) + message(WARNING "Unable to find libuhd") +else() + add_library(uhd_source uhd_source.cc) + target_link_libraries(uhd_source ${UHD_LIBRARIES} publisher stdc++) +endif() + pkg_check_modules(AIRSPY libairspy) if(NOT AIRSPY_FOUND) message(WARNING "Unable to find libairspy") @@ -60,6 +68,10 @@ target_link_libraries(goesrecv clock_recovery) target_link_libraries(goesrecv quantize) target_link_libraries(goesrecv nanomsg_source) target_link_libraries(goesrecv version) +if(UHD_FOUND) + target_compile_definitions(goesrecv PUBLIC -DBUILD_UHD) + target_link_libraries(goesrecv uhd_source) +endif() if(AIRSPY_FOUND) target_compile_definitions(goesrecv PUBLIC -DBUILD_AIRSPY) target_link_libraries(goesrecv airspy_source) diff --git a/src/goesrecv/config.cc b/src/goesrecv/config.cc index ff522585..258431cf 100644 --- a/src/goesrecv/config.cc +++ b/src/goesrecv/config.cc @@ -123,6 +123,43 @@ void loadDemodulator(Config::Demodulator& out, const toml::Value& v) { } } +// +void loadUHDSource(Config::UHD& out, const toml::Value& v) { + const auto& table = v.as(); + + for (const auto& it : table) { + const auto& key = it.first; + const auto& value = it.second; + + if (key == "type") { + out.type = value.as(); + continue; + } + + if (key == "frequency") { + out.frequency = value.as(); + continue; + } + + if (key == "sample_rate") { + out.sampleRate = value.as(); + continue; + } + + if (key == "gain") { + out.gain = value.as(); + continue; + } + + if (key == "sample_publisher") { + out.samplePublisher = createSamplePublisher(value); + continue; + } + + throwInvalidKey(key); + } +} + void loadAirspySource(Config::Airspy& out, const toml::Value& v) { const auto& table = v.as(); for (const auto& it : table) { @@ -382,6 +419,11 @@ Config Config::load(const std::string& file) { continue; } + if (key == "uhd") { + loadUHDSource(out.uhd, value); + continue; + } + if (key == "airspy") { loadAirspySource(out.airspy, value); continue; diff --git a/src/goesrecv/config.h b/src/goesrecv/config.h index 1a026b29..57141441 100644 --- a/src/goesrecv/config.h +++ b/src/goesrecv/config.h @@ -21,7 +21,7 @@ struct Config { // LRIT or HRIT std::string downlinkType; - // String "airspy" or "rtlsdr" + // String "uhd", "airspy" or "rtlsdr" std::string source; // Demodulator statistics (gain, frequency correction, etc.) @@ -33,6 +33,18 @@ struct Config { Demodulator demodulator; + struct UHD { + + std::string type; // device type filter + uint32_t frequency = 0; + uint32_t sampleRate = 0; + uint8_t gain = 8; + + std::unique_ptr samplePublisher; + }; + + UHD uhd; + struct Airspy { uint32_t frequency = 0; uint32_t sampleRate = 0; diff --git a/src/goesrecv/source.cc b/src/goesrecv/source.cc index 28fd91b5..a148b5d5 100644 --- a/src/goesrecv/source.cc +++ b/src/goesrecv/source.cc @@ -2,6 +2,10 @@ #include +#ifdef BUILD_UHD +#include "uhd_source.h" +#endif + #ifdef BUILD_AIRSPY #include "airspy_source.h" #endif @@ -15,6 +19,35 @@ std::unique_ptr Source::build( const std::string& type, Config& config) { + if (type == "uhd") { +#ifdef BUILD_UHD + auto uhd = UHD::open( config.uhd.type ); + + // Use sample rate if set, otherwise default to lowest possible rate. + // auto rates = uhd->getSampleRates(); + + // Use sample rate if set, otherwise default to 2.4MSPS. + if (config.uhd.sampleRate != 0) { + uhd->setSampleRate(config.uhd.sampleRate); + } else { + uhd->setSampleRate(2400000); + } + + uhd->setFrequency(config.uhd.frequency); + uhd->setGain(config.uhd.gain); + uhd->setSamplePublisher(std::move(config.uhd.samplePublisher)); + + return std::unique_ptr(uhd.release()); +#else + throw std::runtime_error( + "You configured goesrecv to use the \"uhd\" source, " + "but goesrecv was not compiled with UHD support. " + "Make sure to install the UHD library before compiling goestools, " + "and look for a message saying 'Found uhd' when running cmake." + ); +#endif + } + if (type == "airspy") { #ifdef BUILD_AIRSPY auto airspy = Airspy::open(); diff --git a/src/goesrecv/uhd_source.cc b/src/goesrecv/uhd_source.cc new file mode 100644 index 00000000..f7d3d36f --- /dev/null +++ b/src/goesrecv/uhd_source.cc @@ -0,0 +1,263 @@ +// +// Support for Ettus Research software defined radio peripherals (USRP) +// Added by Jim Herbert (https://github.com/codient/goestools) +// + +#include "uhd_source.h" +#include +#include +#include +#include +#include +#include +#include + +std::unique_ptr UHD::open(std::string type) { + std::string args = ""; + + // Filter to a device type if one was specified + if (not type.empty()) { + args = "type=" + type; + } + + // Set the scheudling priority on the current thread + uhd::set_thread_priority_safe(); + + // Create a USRP device + uhd::usrp::multi_usrp::sptr dev = nullptr; + + UHD_SAFE_CALL(dev = uhd::usrp::multi_usrp::make(args);) + + // Abort if we didn't open a device + if (dev == nullptr) { + std::cout << "Unable to open UHD device. " << std::endl; + + exit(1); + } + + std::cout << std::endl + << "Using Device " << dev->get_pp_string() << std::endl; + + // Select the internal clock source + dev->set_clock_source("internal"); + + return std::make_unique(dev); +} + +UHD::UHD(uhd::usrp::multi_usrp::sptr dev) : dev_(dev) { + // Perform necessary constructor operations here +} + +UHD::~UHD() { + // Perform necessary destructor operations here +} + +void UHD::setFrequency(uint32_t freq) { + ASSERT(dev_ != nullptr); + + uhd::tune_request_t tune_request(freq); + + dev_->set_rx_freq(tune_request); +} + +void UHD::setSampleRate(uint32_t rate) { + ASSERT(dev_ != nullptr); + + dev_->set_rx_rate(rate); + + // Set the bandwidth to match the sample rate + dev_->set_rx_bandwidth(rate); +} + +uint32_t UHD::getSampleRate() const { + ASSERT(dev_ != nullptr); + + return dev_->get_rx_rate(); +} + +void UHD::setGain(int gain) { + ASSERT(dev_ != nullptr); + + dev_->set_rx_gain(gain); +} + +void UHD::start(const std::shared_ptr> &queue) { + ASSERT(dev_ != nullptr); + + // This is the application's sample queue + queue_ = queue; + + thread_ = std::thread([&] { + // The streamming loop continues to run as long as this is true + running = true; + + // Keep track of the total accumulated samples + size_t num_acc_samps = 0; + + // Set the antenana for receiving our signal + dev_->set_rx_antenna("TX/RX"); + + // Check sensors for a local oscillator + std::vector sensor_names = dev_->get_rx_sensor_names(0); + + // If a local oscillator exists, make sure it's locked + if (std::find(sensor_names.begin(), sensor_names.end(), "lo_locked") != + sensor_names.end()) { + std::cout << std::endl + << "-- Found a local oscillator" << std::endl; + + int lock_attempts = 0; + + do { + if (lock_attempts++ > 3) // Abort after three attempts + { + std::cout << "Unable to lock local oscillator" << std::endl; + + exit(1); + } + + // Allow the front end time to settle + std::this_thread::sleep_for(std::chrono::seconds(1)); + } while (not dev_->get_rx_sensor("lo_locked").to_bool()); + + std::cout << "-- Local oscillator locked" << std::endl << std::endl; + } + + // Specify that we want to receive samples as a pair of 16 bit complex + // floats + uhd::stream_args_t stream_args("fc32"); + + // Create a receive stream + uhd::rx_streamer::sptr rx_stream = dev_->get_rx_stream(stream_args); + + // Create a stream command that will start immediately and run + // continuously + uhd::stream_cmd_t stream_cmd_start( + uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + + stream_cmd_start.stream_now = true; + + // Start streaming + rx_stream->issue_stream_cmd(stream_cmd_start); + + // Holds stream metadata, such as error codes + uhd::rx_metadata_t md; + + // Create an internal buffer to hold received samples + std::vector> buff(rx_stream->get_max_num_samps() * + 2); + + while (running) { + // Read samples from the device and into our buffer + size_t num_rx_samps = + rx_stream->recv(&buff.front(), buff.size(), md); + + // Handle any error codes we receive + switch (md.error_code) { + case uhd::rx_metadata_t::ERROR_CODE_NONE: + break; + + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + if (num_acc_samps == 0) + continue; + + std::cerr << "Timeout before all samples received, possible " + "packet loss" + << std::endl; + + goto done_loop; + + case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: + if (md.out_of_sequence == true) + std::cerr << "Samples out of sequence" << std::endl; + else + std::cerr << "Sample buffer overflow. If this persists, " + "try reducing the sample rate." + << std::endl; + num_rx_samps = 0; + + continue; + + default: + std::cerr << "Received error code " << md.error_code + << std::endl; + std::cerr << "flags " << md.out_of_sequence << std::endl; + goto done_loop; + } + + // The receiving queue is expecting a number of samples evenly + // divisible by four + if (num_rx_samps % 4 != 0) + continue; + + // Copies samples from our internal buffer into the application's + // queue + transfer_buffer(buff, num_rx_samps); + + // Increment the accumulated sample count + num_acc_samps += num_rx_samps; + } + done_loop: + + // setup streaming + uhd::stream_cmd_t stream_cmd_stop( + uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + + // stream_cmd.stream_now = true; + + rx_stream->issue_stream_cmd(stream_cmd_stop); + + // finished + std::cout << std::endl << "Streaming ended" << std::endl << std::endl; + + return EXIT_SUCCESS; + }); + +#ifdef __APPLE__ + pthread_setname_np("uhd"); +#else + pthread_setname_np(thread_.native_handle(), "uhd"); +#endif +} + +void UHD::stop() { + ASSERT(dev_ != nullptr); + + // Causes the sample streaming loop to exit + running = false; + + // Wait for thread to terminate + thread_.join(); + + // Close queue to signal downstream + queue_->close(); + + // Clear reference to queue + queue_.reset(); +} + +// Copies samples from our internal buffer into the application's queue +void UHD::transfer_buffer(std::vector> transfer, + size_t num_rx_samps) { + // Expect multiple of 2 + ASSERT((num_rx_samps & 0x2) == 0); + + // Grab buffer from queue + auto out = queue_->popForWrite(); + + out->resize(num_rx_samps); + + std::complex *p = out->data(); + + for (int i = 0; i < int(num_rx_samps); i++) { + p[i] = transfer[i]; + } + + // Publish output if applicable + if (samplePublisher_) { + samplePublisher_->publish(*out); + } + + // Return buffer to queue + queue_->pushWrite(std::move(out)); +} diff --git a/src/goesrecv/uhd_source.h b/src/goesrecv/uhd_source.h new file mode 100644 index 00000000..1447da45 --- /dev/null +++ b/src/goesrecv/uhd_source.h @@ -0,0 +1,60 @@ +#pragma once + +#include "source.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class UHD : public Source { + public: + static std::unique_ptr open(std::string type); + + explicit UHD(uhd::usrp::multi_usrp::sptr dev); + + ~UHD(); + + void setFrequency(uint32_t freq); + + // Also sets the bandwidth to match + void setSampleRate(uint32_t rate); + + // Reads the current sample rate setting from the device + virtual uint32_t getSampleRate() const override; + + void setGain(int gain); + + void setSamplePublisher(std::unique_ptr samplePublisher) { + samplePublisher_ = std::move(samplePublisher); + } + + // Starts the sample streaming loop + virtual void start(const std::shared_ptr> &queue) override; + + // Stops the sample streaming loop + virtual void stop() override; + + protected: + // When set to false the sample streaming loop exits + bool running; + + // A reference to the device + uhd::usrp::multi_usrp::sptr dev_; + + // Background RX thread + std::thread thread_; + + // Copies samples from our internal buffer into the application's queue + virtual void transfer_buffer(std::vector> transfer, + size_t num_rx_samps); + + // Set on start; cleared on stop + std::shared_ptr> queue_; + + // Optional publisher for samples + std::unique_ptr samplePublisher_; +};