From d73b6dff480c6b53a3d5bdea7ab3f51b72e6949d Mon Sep 17 00:00:00 2001 From: nqbit <1446767+nqbit@users.noreply.github.com> Date: Wed, 26 Jun 2019 07:17:34 +0100 Subject: [PATCH] Add support for HackRF. --- docs/commands/goesrecv.rst | 14 ++-- docs/installation.rst | 3 +- etc/goesrecv.conf | 8 ++ scripts/setup_raspbian.sh | 1 + src/goesrecv/CMakeLists.txt | 13 +++ src/goesrecv/config.cc | 76 +++++++++++++++--- src/goesrecv/config.h | 22 ++++- src/goesrecv/hackrf_source.cc | 146 ++++++++++++++++++++++++++++++++++ src/goesrecv/hackrf_source.h | 61 ++++++++++++++ src/goesrecv/source.cc | 78 +++++++++++++----- 10 files changed, 385 insertions(+), 37 deletions(-) create mode 100644 src/goesrecv/hackrf_source.cc create mode 100644 src/goesrecv/hackrf_source.h diff --git a/docs/commands/goesrecv.rst b/docs/commands/goesrecv.rst index 587f2e56..37f28369 100644 --- a/docs/commands/goesrecv.rst +++ b/docs/commands/goesrecv.rst @@ -13,17 +13,19 @@ Demodulate and decode signal into packet stream. See :ref:`minimal_receiver` for an example setup. You can use goesrecv with an RTL-SDR_ (make sure you have one with the -R820T tuner chip), or an Airspy_ (confirmed to work with the Mini). -The raw signal is then processed by the demodulator and turned into a -stream of 1s and 0s. This is then passed to the decoder where the -bitstream is synchronized and error correction is applied. Every valid -packet is then forwarded to downstream tools (e.g. goeslrit or -goesproc). +R820T tuner chip), an Airspy_ (confirmed to work with the Mini), or a +HackRf_ (confirmed to work with a HackRf One). The raw signal is then +processed by the demodulator and turned into a stream of 1s and 0s. This +is then passed to the decoder where the bitstream is synchronized and +error correction is applied. Every valid packet is then forwarded to +downstream tools (e.g. goeslrit or goesproc). .. _rtl-sdr: https://rtlsdr.org/ .. _airspy: https://airspy.com/ +.. _hackrf: + https://greatscottgadgets.com/hackrf/ Options ======= diff --git a/docs/installation.rst b/docs/installation.rst index 4684e424..c2c3bdea 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -51,7 +51,8 @@ Install system dependencies: If you want to run goesrecv on this machine, you also have to install the development packages of the drivers the SDRs you want to use; -``librtlsdr-dev`` for an RTL-SDR, ``libairspy-dev`` for an Airspy. +``librtlsdr-dev`` for an RTL-SDR, ``libairspy-dev`` for an Airspy, +and ``libhackrf-dev`` for a HackRf. Now you can build and install goestools: diff --git a/etc/goesrecv.conf b/etc/goesrecv.conf index 354d11d1..cdb790dd 100644 --- a/etc/goesrecv.conf +++ b/etc/goesrecv.conf @@ -33,6 +33,14 @@ source = "airspy" # bias_tee = false # device_index = 0 +# [hackrf] +# frequency = 1694100000 +# sample_rate = 8000000 +# if_gain = 32 +# bb_gain = 34 +# rf_amp_enabled = true +# bias_tee = false + # [nanomsg] # sample_rate = 2400000 # connect = "tcp://1.2.3.4:5005" diff --git a/scripts/setup_raspbian.sh b/scripts/setup_raspbian.sh index e701a810..790d5a14 100755 --- a/scripts/setup_raspbian.sh +++ b/scripts/setup_raspbian.sh @@ -23,6 +23,7 @@ urls() { scripts/list_raspbian_urls.py \ librtlsdr-dev \ libairspy-dev \ + libhackrf-dev \ libusb-1.0-0-dev \ libudev1 \ zlib1g-dev \ diff --git a/src/goesrecv/CMakeLists.txt b/src/goesrecv/CMakeLists.txt index b8a0934e..326b64e4 100644 --- a/src/goesrecv/CMakeLists.txt +++ b/src/goesrecv/CMakeLists.txt @@ -29,6 +29,14 @@ else() target_link_libraries(rtlsdr_source ${RTLSDR_LIBRARIES} publisher stdc++) endif() +pkg_check_modules(HACKRF libhackrf) +if(NOT HACKRF_FOUND) + message(WARNING "Unable to find libhackrf") +else() + add_library(hackrf_source hackrf_source.cc) + target_link_libraries(hackrf_source ${HACKRF_LIBRARIES} publisher stdc++) +endif() + add_library(nanomsg_source nanomsg_source.cc) target_link_libraries(nanomsg_source nanomsg publisher stdc++) @@ -60,6 +68,7 @@ target_link_libraries(goesrecv clock_recovery) target_link_libraries(goesrecv quantize) target_link_libraries(goesrecv nanomsg_source) target_link_libraries(goesrecv version) + if(AIRSPY_FOUND) target_compile_definitions(goesrecv PUBLIC -DBUILD_AIRSPY) target_link_libraries(goesrecv airspy_source) @@ -68,6 +77,10 @@ if(RTLSDR_FOUND) target_compile_definitions(goesrecv PUBLIC -DBUILD_RTLSDR) target_link_libraries(goesrecv rtlsdr_source) endif() +if(HACKRF_FOUND) + target_compile_definitions(goesrecv PUBLIC -DBUILD_HACKRF) + target_link_libraries(goesrecv hackrf_source) +endif() add_executable(benchmark benchmark.cc) target_link_libraries(benchmark pthread) diff --git a/src/goesrecv/config.cc b/src/goesrecv/config.cc index ff522585..d7c3179e 100644 --- a/src/goesrecv/config.cc +++ b/src/goesrecv/config.cc @@ -1,11 +1,11 @@ #include "config.h" +#include + #include #include #include -#include - namespace { void throwInvalidKey(const std::string& key) { @@ -24,7 +24,8 @@ void setIfZero(T& out, T value) { std::unique_ptr createSamplePublisher(const toml::Value& v) { auto bind = v.find("bind"); if (!bind) { - throw std::invalid_argument("Expected publisher section to have \"bind\" key"); + throw std::invalid_argument( + "Expected publisher section to have \"bind\" key"); } // Only need bind value to create publisher @@ -42,7 +43,8 @@ std::unique_ptr createSamplePublisher(const toml::Value& v) { std::unique_ptr createSoftBitPublisher(const toml::Value& v) { auto bind = v.find("bind"); if (!bind) { - throw std::invalid_argument("Expected publisher section to have \"bind\" key"); + throw std::invalid_argument( + "Expected publisher section to have \"bind\" key"); } // Only need bind value to create publisher @@ -60,7 +62,8 @@ std::unique_ptr createSoftBitPublisher(const toml::Value& v) { std::unique_ptr createPacketPublisher(const toml::Value& v) { auto bind = v.find("bind"); if (!bind) { - throw std::invalid_argument("Expected publisher section to have \"bind\" key"); + throw std::invalid_argument( + "Expected publisher section to have \"bind\" key"); } // Only need bind value to create publisher @@ -78,7 +81,8 @@ std::unique_ptr createPacketPublisher(const toml::Value& v) { Config::StatsPublisher createStatsPublisher(const toml::Value& v) { auto bind = v.find("bind"); if (!bind) { - throw std::invalid_argument("Expected publisher section to have \"bind\" key"); + throw std::invalid_argument( + "Expected publisher section to have \"bind\" key"); } Config::StatsPublisher out; @@ -198,6 +202,51 @@ void loadRTLSDRSource(Config::RTLSDR& out, const toml::Value& v) { } } +void loadHackRfSource(Config::HackRf& 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 == "frequency") { + out.frequency = value.as(); + continue; + } + + if (key == "sample_rate") { + out.sampleRate = value.as(); + continue; + } + + if (key == "if_gain") { + out.if_gain = value.as(); + continue; + } + + if (key == "bb_gain") { + out.bb_gain = value.as(); + continue; + } + + if (key == "rf_amp_enabled") { + out.rf_amp_enabled = value.as(); + continue; + } + + if (key == "sample_publisher") { + out.samplePublisher = createSamplePublisher(value); + continue; + } + + if (key == "bias_tee") { + out.bias_tee = value.as(); + continue; + } + + throwInvalidKey(key); + } +} + void loadNanomsgSource(Config::Nanomsg& out, const toml::Value& v) { const auto& table = v.as(); for (const auto& it : table) { @@ -241,12 +290,12 @@ void loadAGC(Config::AGC& out, const toml::Value& v) { const auto& value = it.second; if (key == "min") { - out.min = (float) value.as(); + out.min = (float)value.as(); continue; } if (key == "max") { - out.max = (float) value.as(); + out.max = (float)value.as(); continue; } @@ -266,7 +315,7 @@ void loadCostas(Config::Costas& out, const toml::Value& v) { const auto& value = it.second; if (key == "max_deviation") { - out.maxDeviation = (int) value.as(); + out.maxDeviation = (int)value.as(); if (out.maxDeviation <= 0) { throw std::invalid_argument("Expected 'max_deviation' to be positive"); } @@ -362,7 +411,7 @@ void loadMonitor(Config::Monitor& out, const toml::Value& v) { } } -} // namespace +} // namespace Config Config::load(const std::string& file) { Config out; @@ -392,6 +441,11 @@ Config Config::load(const std::string& file) { continue; } + if (key == "hackrf") { + loadHackRfSource(out.hackrf, value); + continue; + } + if (key == "nanomsg") { loadNanomsgSource(out.nanomsg, value); continue; @@ -446,10 +500,12 @@ Config Config::load(const std::string& file) { if (out.demodulator.downlinkType == "lrit") { setIfZero(out.airspy.frequency, 1691000000u); setIfZero(out.rtlsdr.frequency, 1691000000u); + setIfZero(out.hackrf.frequency, 1691000000u); } if (out.demodulator.downlinkType == "hrit") { setIfZero(out.airspy.frequency, 1694100000u); setIfZero(out.rtlsdr.frequency, 1694100000u); + setIfZero(out.hackrf.frequency, 1694100000u); } return out; diff --git a/src/goesrecv/config.h b/src/goesrecv/config.h index 1a026b29..f548351e 100644 --- a/src/goesrecv/config.h +++ b/src/goesrecv/config.h @@ -44,10 +44,30 @@ struct Config { bool bias_tee = 0; std::unique_ptr samplePublisher; - }; + }; Airspy airspy; + struct HackRf { + uint32_t frequency = 0; + uint32_t sampleRate = 0; + + // Applies to the tuner IF gain setting + uint8_t if_gain = 8; + + // Applies to the tuner BB gain setting + uint8_t bb_gain = 30; + + // Enables or disables the RF amplifier + bool rf_amp_enabled = 0; + + bool bias_tee = 0; + + std::unique_ptr samplePublisher; + }; + + HackRf hackrf; + struct RTLSDR { uint32_t frequency = 0; uint32_t sampleRate = 0; diff --git a/src/goesrecv/hackrf_source.cc b/src/goesrecv/hackrf_source.cc new file mode 100644 index 00000000..04b4e763 --- /dev/null +++ b/src/goesrecv/hackrf_source.cc @@ -0,0 +1,146 @@ +#include "hackrf_source.h" + +#include +#include + +#include +#include + +std::unique_ptr HackRf::open(uint32_t index) { + struct hackrf_device* dev = nullptr; + auto rv_init = hackrf_init(); + if (rv_init < 0) { + std::cerr << "Unable to init hackrf" << std::endl; + exit(1); + } + + auto rv = hackrf_open(&dev); + + if (rv < 0) { + std::cerr << "Unable to open HackRf device: " + << hackrf_error_name((enum hackrf_error)rv) << std::endl; + exit(1); + } + + return std::make_unique(dev); +} + +HackRf::HackRf(struct hackrf_device* dev) : dev_(dev) { + // Load list of supported sample rates + sampleRates_ = loadSampleRates(); +} + +HackRf::~HackRf() { + if (dev_ != nullptr) { + hackrf_close(dev_); + hackrf_exit(); + } +} + +std::vector HackRf::loadSampleRates() { + return {8000000, 10000000, 12500000, 16000000, 20000000}; +} + +void HackRf::setFrequency(uint32_t freq) { + ASSERT(dev_ != nullptr); + auto rv = hackrf_set_freq(dev_, freq); + ASSERT(rv >= 0); +} + +void HackRf::setSampleRate(uint32_t rate) { + ASSERT(dev_ != nullptr); + auto rv = hackrf_set_sample_rate(dev_, rate); + ASSERT(rv >= 0); + sampleRate_ = rate; +} + +uint32_t HackRf::getSampleRate() const { return sampleRate_; } + +void HackRf::setIfGain(int gain) { + ASSERT(dev_ != nullptr); + auto rv = hackrf_set_lna_gain(dev_, gain); + ASSERT(rv >= 0); +} + +void HackRf::setRfAmplifier(bool on) { + ASSERT(dev_ != nullptr); + auto rv = hackrf_set_amp_enable(dev_, on); + ASSERT(rv >= 0); +} + +void HackRf::setBbGain(int gain) { + ASSERT(dev_ != nullptr); + auto rv = hackrf_set_vga_gain(dev_, gain); + ASSERT(rv >= 0); +} + +void HackRf::setBiasTee(bool on) { + ASSERT(dev_ != nullptr); + auto rv = hackrf_set_antenna_enable(dev_, on ? 1 : 0); + ASSERT(rv >= 0); +} + +static int hackrf_callback(hackrf_transfer* transfer) { + auto hackrf_context = reinterpret_cast(transfer->rx_ctx); + hackrf_context->handle(transfer); + return 0; +} + +void HackRf::start(const std::shared_ptr >& queue) { + ASSERT(dev_ != nullptr); + queue_ = queue; + thread_ = std::thread([&] { + auto rv = hackrf_start_rx(dev_, &hackrf_callback, this); + ASSERT(rv == 0); + }); +#ifdef __APPLE__ + pthread_setname_np("hackrf"); +#else + pthread_setname_np(thread_.native_handle(), "hackrf"); +#endif +} + +void HackRf::stop() { + ASSERT(dev_ != nullptr); + auto rv = hackrf_stop_rx(dev_); + ASSERT(rv >= 0); + + // Wait for thread to terminate + thread_.join(); + + // Close queue to signal downstream + queue_->close(); + + // Clear reference to queue + queue_.reset(); +} + +void HackRf::process(size_t nsamples, unsigned char* buf, + std::complex* fo) { + for (uint32_t i = 0; i < nsamples; i++) { + fo[i].real((static_cast(buf[i * 2 + 0]) / 128.0f)); + fo[i].imag((static_cast(buf[i * 2 + 1]) / 128.0f)); + } +} + +void HackRf::handle(const hackrf_transfer* transfer) { + uint32_t nsamples = transfer->valid_length / 2; + + // Expect multiple of 2 + ASSERT((nsamples & 0x2) == 0); + + // Grab buffer from queue + auto out = queue_->popForWrite(); + out->resize(nsamples); + + // Convert unsigned char to std::complex + process(nsamples, transfer->buffer, out->data()); + + // Publish output if applicable + if (samplePublisher_) { + samplePublisher_->publish(*out); + } + + // Return buffer to queue + queue_->pushWrite(std::move(out)); +} diff --git a/src/goesrecv/hackrf_source.h b/src/goesrecv/hackrf_source.h new file mode 100644 index 00000000..0dc43275 --- /dev/null +++ b/src/goesrecv/hackrf_source.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include "source.h" + +class HackRf : public Source { + public: + static std::unique_ptr open(uint32_t index = 0); + + explicit HackRf(struct hackrf_device* dev); + ~HackRf(); + + std::vector getSampleRates() const { return sampleRates_; } + + void setFrequency(uint32_t freq); + + void setSampleRate(uint32_t rate); + + virtual uint32_t getSampleRate() const override; + + void setBbGain(int gain); + + void setIfGain(int gain); + + void setRfAmplifier(bool on); + + void setBiasTee(bool on); + + void setSamplePublisher(std::unique_ptr samplePublisher) { + samplePublisher_ = std::move(samplePublisher); + } + + virtual void start(const std::shared_ptr >& queue) override; + + virtual void stop() override; + + void handle(const hackrf_transfer* transfer); + + protected: + struct hackrf_device* dev_; + + std::vector loadSampleRates(); + void process(size_t nsamples, unsigned char* buf, std::complex* fo); + std::vector sampleRates_; + std::uint32_t sampleRate_; + + // Background RX thread + std::thread thread_; + + // Set on start; cleared on stop + std::shared_ptr > queue_; + + // Optional publisher for samples + std::unique_ptr samplePublisher_; +}; diff --git a/src/goesrecv/source.cc b/src/goesrecv/source.cc index 28fd91b5..f0dc9d01 100644 --- a/src/goesrecv/source.cc +++ b/src/goesrecv/source.cc @@ -2,6 +2,10 @@ #include +#ifdef BUILD_HACKRF +#include "hackrf_source.h" +#endif + #ifdef BUILD_AIRSPY #include "airspy_source.h" #endif @@ -12,9 +16,7 @@ #include "nanomsg_source.h" -std::unique_ptr Source::build( - const std::string& type, - Config& config) { +std::unique_ptr Source::build(const std::string& type, Config& config) { if (type == "airspy") { #ifdef BUILD_AIRSPY auto airspy = Airspy::open(); @@ -27,10 +29,9 @@ std::unique_ptr Source::build( auto pos = std::find(rates.begin(), rates.end(), rate); if (pos == rates.end()) { std::stringstream ss; - ss << - "You configured the Airspy source to use an unsupported " << - "sample rate equal to " << rate << ". " << - "Supported sample rates are: " << std::endl; + ss << "You configured the Airspy source to use an unsupported " + << "sample rate equal to " << rate << ". " + << "Supported sample rates are: " << std::endl; for (size_t i = 0; i < rates.size(); i++) { ss << " - " << rates[i] << std::endl; } @@ -48,13 +49,54 @@ std::unique_ptr Source::build( return std::unique_ptr(airspy.release()); #else throw std::runtime_error( - "You configured goesrecv to use the \"airspy\" source, " - "but goesrecv was not compiled with Airspy support. " - "Make sure to install the Airspy library before compiling goestools, " - "and look for a message saying 'Found libairspy' when running cmake." - ); + "You configured goesrecv to use the \"airspy\" source, " + "but goesrecv was not compiled with Airspy support. " + "Make sure to install the Airspy library before compiling goestools, " + "and look for a message saying 'Found libairspy' when running cmake."); #endif } + + if (type == "hackrf") { +#ifdef BUILD_HACKRF + auto hackrf = HackRf::open(); + + // Use sample rate if set, otherwise default to lowest possible rate. + auto rates = hackrf->getSampleRates(); + if (config.hackrf.sampleRate != 0) { + auto rate = config.hackrf.sampleRate; + auto pos = std::find(rates.begin(), rates.end(), rate); + if (pos == rates.end()) { + std::stringstream ss; + ss << "You configured the HackRf source to use an unsupported " + << "sample rate equal to " << rate << ". " + << "Supported sample rates are: " << std::endl; + for (size_t i = 0; i < rates.size(); i++) { + ss << " - " << rates[i] << std::endl; + } + throw std::runtime_error(ss.str()); + } + hackrf->setSampleRate(rate); + } else { + std::sort(rates.begin(), rates.end()); + hackrf->setSampleRate(rates[0]); + } + + hackrf->setFrequency(config.hackrf.frequency); + hackrf->setRfAmplifier(config.hackrf.rf_amp_enabled); + hackrf->setIfGain(config.hackrf.if_gain); + hackrf->setBbGain(config.hackrf.bb_gain); + hackrf->setBiasTee(config.hackrf.bias_tee); + hackrf->setSamplePublisher(std::move(config.airspy.samplePublisher)); + return std::unique_ptr(hackrf.release()); +#else + throw std::runtime_error( + "You configured goesrecv to use the \"hackrf\" source, " + "but goesrecv was not compiled with HackRf support. " + "Make sure to install the HackRf library before compiling goestools, " + "and look for a message saying 'Found libhackrf' when running cmake."); +#endif + } + if (type == "rtlsdr") { #ifdef BUILD_RTLSDR auto rtlsdr = RTLSDR::open(config.rtlsdr.deviceIndex); @@ -72,11 +114,10 @@ std::unique_ptr Source::build( return std::unique_ptr(rtlsdr.release()); #else throw std::runtime_error( - "You configured goesrecv to use the \"rtlsdr\" source, " - "but goesrecv was not compiled with RTL-SDR support. " - "Make sure to install the RTL-SDR library before compiling goestools, " - "and look for a message saying 'Found librtlsdr' when running cmake." - ); + "You configured goesrecv to use the \"rtlsdr\" source, " + "but goesrecv was not compiled with RTL-SDR support. " + "Make sure to install the RTL-SDR library before compiling goestools, " + "and look for a message saying 'Found librtlsdr' when running cmake."); #endif } if (type == "nanomsg") { @@ -89,5 +130,4 @@ std::unique_ptr Source::build( throw std::runtime_error("Invalid source: " + type); } -Source::~Source() { -} +Source::~Source() {}