From 9ccc14848b2b6334627a62cd1cc45320281dab31 Mon Sep 17 00:00:00 2001 From: AlexandreRouma Date: Sun, 23 Oct 2022 16:41:45 +0200 Subject: [PATCH] USRP source --- .github/workflows/build_all.yml | 2 +- CMakeLists.txt | 7 +- docker_builds/debian_bullseye/do_build.sh | 4 +- docker_builds/debian_buster/do_build.sh | 4 +- docker_builds/debian_sid/do_build.sh | 4 +- docker_builds/ubuntu_bionic/do_build.sh | 4 +- docker_builds/ubuntu_focal/do_build.sh | 4 +- docker_builds/ubuntu_jammy/do_build.sh | 4 +- source_modules/hermes_source/src/hermes.cpp | 18 +- source_modules/hermes_source/src/hermes.h | 7 + source_modules/hermes_source/src/main.cpp | 145 +++++-- source_modules/hermes_source/src/net.cpp | 176 +++++--- source_modules/hermes_source/src/net.h | 142 ++++++- source_modules/usrp_source/CMakeLists.txt | 38 ++ source_modules/usrp_source/src/main.cpp | 429 ++++++++++++++++++++ 15 files changed, 862 insertions(+), 126 deletions(-) create mode 100644 source_modules/usrp_source/CMakeLists.txt create mode 100644 source_modules/usrp_source/src/main.cpp diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 1726ac09..4802f5d1 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -87,7 +87,7 @@ jobs: run: brew update - name: Install dependencies - run: brew install libusb fftw glfw airspy airspyhf portaudio hackrf rtl-sdr libbladerf codec2 && pip3 install mako zstd + run: brew install libusb fftw glfw airspy airspyhf portaudio hackrf rtl-sdr libbladerf codec2 && pip3 install mako zstd uhd - name: Install volk run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ diff --git a/CMakeLists.txt b/CMakeLists.txt index 104370e2..eceb3080 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libs option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" ON) option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON) option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON) +option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF) # Sinks option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF) @@ -133,6 +134,10 @@ if (OPT_BUILD_PLUTOSDR_SOURCE) add_subdirectory("source_modules/plutosdr_source") endif (OPT_BUILD_PLUTOSDR_SOURCE) +if (OPT_BUILD_USRP_SOURCE) +add_subdirectory("source_modules/usrp_source") +endif (OPT_BUILD_USRP_SOURCE) + # Sink modules if (OPT_BUILD_ANDROID_AUDIO_SINK) @@ -248,7 +253,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_custom_target(do_always ALL cp \"$/libsdrpp_core.dylib\" \"$\") endif () -# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON +# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON # Install directives install(TARGETS sdrpp DESTINATION bin) diff --git a/docker_builds/debian_bullseye/do_build.sh b/docker_builds/debian_bullseye/do_build.sh index 05a0a32e..fbbc385d 100644 --- a/docker_builds/debian_bullseye/do_build.sh +++ b/docker_builds/debian_bullseye/do_build.sh @@ -6,7 +6,7 @@ cd /root apt update apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ - libcodec2-dev + libcodec2-dev libuhd-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -18,7 +18,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON make VERBOSE=1 -j2 cd .. diff --git a/docker_builds/debian_buster/do_build.sh b/docker_builds/debian_buster/do_build.sh index cbefd63f..5f5dfc34 100644 --- a/docker_builds/debian_buster/do_build.sh +++ b/docker_builds/debian_buster/do_build.sh @@ -6,7 +6,7 @@ cd /root apt update apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ - libcodec2-dev + libcodec2-dev libuhd-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -18,7 +18,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON +cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON make VERBOSE=1 -j2 cd .. diff --git a/docker_builds/debian_sid/do_build.sh b/docker_builds/debian_sid/do_build.sh index 05a0a32e..fbbc385d 100644 --- a/docker_builds/debian_sid/do_build.sh +++ b/docker_builds/debian_sid/do_build.sh @@ -6,7 +6,7 @@ cd /root apt update apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ - libcodec2-dev + libcodec2-dev libuhd-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -18,7 +18,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON make VERBOSE=1 -j2 cd .. diff --git a/docker_builds/ubuntu_bionic/do_build.sh b/docker_builds/ubuntu_bionic/do_build.sh index 2145328a..f06098b4 100644 --- a/docker_builds/ubuntu_bionic/do_build.sh +++ b/docker_builds/ubuntu_bionic/do_build.sh @@ -12,7 +12,7 @@ apt update # Install dependencies and tools apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspy-dev \ libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ - libcodec2-dev libudev-dev + libcodec2-dev libudev-dev libuhd-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -56,7 +56,7 @@ echo 'Cflags: -I/usr/include/codec2' >> /usr/share/pkgconfig/codec2.pc cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON +cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON make VERBOSE=1 -j2 # Generate package diff --git a/docker_builds/ubuntu_focal/do_build.sh b/docker_builds/ubuntu_focal/do_build.sh index 05a0a32e..fbbc385d 100644 --- a/docker_builds/ubuntu_focal/do_build.sh +++ b/docker_builds/ubuntu_focal/do_build.sh @@ -6,7 +6,7 @@ cd /root apt update apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ - libcodec2-dev + libcodec2-dev libuhd-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -18,7 +18,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON make VERBOSE=1 -j2 cd .. diff --git a/docker_builds/ubuntu_jammy/do_build.sh b/docker_builds/ubuntu_jammy/do_build.sh index 05a0a32e..fbbc385d 100644 --- a/docker_builds/ubuntu_jammy/do_build.sh +++ b/docker_builds/ubuntu_jammy/do_build.sh @@ -6,7 +6,7 @@ cd /root apt update apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ - libcodec2-dev + libcodec2-dev libuhd-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -18,7 +18,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON make VERBOSE=1 -j2 cd .. diff --git a/source_modules/hermes_source/src/hermes.cpp b/source_modules/hermes_source/src/hermes.cpp index 4d32f30a..b251bb9a 100644 --- a/source_modules/hermes_source/src/hermes.cpp +++ b/source_modules/hermes_source/src/hermes.cpp @@ -27,6 +27,10 @@ namespace hermes { sendMetisControl(METIS_CTRL_NONE); } + void Client::setSamplerate(HermesLiteSamplerate samplerate) { + writeReg(0, (uint32_t)samplerate << 24); + } + void Client::setFrequency(double freq) { writeReg(HL_REG_TX1_NCO_FREQ, freq); } @@ -122,7 +126,6 @@ namespace hermes { } // Decode and send IQ to stream - // TODO: More efficient way? uint8_t* iq = &frame[8]; for (int i = 0; i < 63; i++) { // Convert to 32bit @@ -133,11 +136,12 @@ namespace hermes { si = (si << 8) >> 8; sq = (sq << 8) >> 8; - // Convert to float (IQ swapper for some reason... I means in-phase :facepalm:) + // Convert to float (IQ swapper for some reason... 'I' means in-phase... :facepalm:) out.writeBuf[i].im = (float)si / (float)0x1000000; out.writeBuf[i].re = (float)sq / (float)0x1000000; } out.swap(63); + // TODO: Buffer the data to avoid having a very high DSP frame rate } } } @@ -160,8 +164,9 @@ namespace hermes { while (true) { // Wait for a response + net::Address addr; uint8_t resp[1024]; - int len = sock->recv(resp, sizeof(resp), false, HERMES_DISCOVER_TIMEOUT); + int len = sock->recv(resp, sizeof(resp), false, HERMES_DISCOVER_TIMEOUT, &addr); // Give up if timeout or error if (len <= 0) { break; } @@ -172,6 +177,7 @@ namespace hermes { // Analyze Info info; + info.addr = addr; memcpy(info.mac, &resp[3], 6); info.gatewareVerMaj = resp[0x09]; info.gatewareVerMin = resp[0x15]; @@ -194,8 +200,12 @@ namespace hermes { } std::shared_ptr open(std::string host, int port) { + return open(net::Address(host, port)); + } + + std::shared_ptr open(const net::Address& addr) { // Open UDP socket - auto sock = net::openudp(host, port); + auto sock = net::openudp(addr); // TODO: Check if open successful return std::make_shared(sock); diff --git a/source_modules/hermes_source/src/hermes.h b/source_modules/hermes_source/src/hermes.h index 9ec6d156..998271f2 100644 --- a/source_modules/hermes_source/src/hermes.h +++ b/source_modules/hermes_source/src/hermes.h @@ -32,10 +32,15 @@ namespace hermes { }; struct Info { + net::Address addr; uint8_t mac[6]; uint8_t gatewareVerMaj; uint8_t gatewareVerMin; BoardID boardId; + + bool operator==(const Info& b) { + return !memcmp(mac, b.mac, 6); + } }; enum HermesLiteReg { @@ -122,6 +127,7 @@ namespace hermes { void start(); void stop(); + void setSamplerate(HermesLiteSamplerate samplerate); void setFrequency(double freq); void setGain(int gain); @@ -146,4 +152,5 @@ namespace hermes { std::vector discover(); std::shared_ptr open(std::string host, int port); + std::shared_ptr open(const net::Address& addr); } \ No newline at end of file diff --git a/source_modules/hermes_source/src/main.cpp b/source_modules/hermes_source/src/main.cpp index d221333f..a66ce5f5 100644 --- a/source_modules/hermes_source/src/main.cpp +++ b/source_modules/hermes_source/src/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #define CONCAT(a, b) ((std::string(a) + b).c_str()) @@ -27,6 +28,14 @@ public: HermesSourceModule(std::string name) { this->name = name; + // Define samplerates + samplerates.define(48000, "48KHz", hermes::HL_SAMP_RATE_48KHZ); + samplerates.define(96000, "96KHz", hermes::HL_SAMP_RATE_96KHZ); + samplerates.define(192000, "192KHz", hermes::HL_SAMP_RATE_192KHZ); + samplerates.define(384000, "384KHz", hermes::HL_SAMP_RATE_384KHZ); + + srId = samplerates.keyId(384000); + lnk.init(NULL, &stream); sampleRate = 384000.0; @@ -40,9 +49,14 @@ public: handler.tuneHandler = tune; handler.stream = &stream; + // TODO: Move the refresh and first select to the select event instead refresh(); - // TODO: Select device + // Select device + config.acquire(); + selectedMac = config.conf["device"]; + config.release(); + selectMac(selectedMac); sigpath::sourceManager.registerSource("Hermes", &handler); } @@ -54,12 +68,6 @@ public: void postInit() {} - enum AGCMode { - AGC_MODE_OFF, - AGC_MODE_LOW, - AGC_MODE_HIGG - }; - void enable() { enabled = true; } @@ -72,16 +80,56 @@ public: return enabled; } - void refresh() { - devList.clear(); - devListTxt = ""; - - // TOOD: Update dev list - } - // TODO: Implement select functions private: + void refresh() { + char mac[128]; + char buf[128]; + devices.clear(); + auto devList = hermes::discover(); + for (auto& d : devList) { + sprintf(mac, "%02X%02X%02X%02X%02X%02X", d.mac[0], d.mac[1], d.mac[2], d.mac[3], d.mac[4], d.mac[5]); + sprintf(buf, "Hermes-Lite 2 [%s]", mac); + devices.define(mac, buf, d); + } + } + + void selectMac(std::string mac) { + // If the device list is empty, don't select anything + if (!devices.size()) { + selectedMac.clear(); + return; + } + + // If the mac doesn't exist, select the first available one instead + if (!devices.keyExists(mac)) { + selectMac(devices.key(0)); + return; + } + + // Default config + srId = samplerates.valueId(hermes::HL_SAMP_RATE_384KHZ); + gain = 0; + + // Load config + devId = devices.keyId(mac); + selectedMac = mac; + config.acquire(); + if (config.conf["devices"][selectedMac].contains("samplerate")) { + int sr = config.conf["devices"][selectedMac]["samplerate"]; + if (samplerates.keyExists(sr)) { srId = samplerates.keyId(sr); } + } + if (config.conf["devices"][selectedMac].contains("gain")) { + gain = config.conf["devices"][selectedMac]["gain"]; + } + config.release(); + + // Update host samplerate + sampleRate = samplerates.key(srId); + core::setInputSampleRate(sampleRate); + } + static void menuSelected(void* ctx) { HermesSourceModule* _this = (HermesSourceModule*)ctx; core::setInputSampleRate(_this->sampleRate); @@ -95,10 +143,10 @@ private: static void start(void* ctx) { HermesSourceModule* _this = (HermesSourceModule*)ctx; - if (_this->running) { return; } + if (_this->running || _this->selectedMac.empty()) { return; } // TODO: Implement start - _this->dev = hermes::open("192.168.0.144", 1024); + _this->dev = hermes::open(_this->devices[_this->devId].addr); // TODO: STOP USING A LINK, FIND A BETTER WAY _this->lnk.setInput(&_this->dev->out); @@ -106,9 +154,9 @@ private: _this->dev->start(); // TODO: Check if the USB commands are accepted before start + _this->dev->setSamplerate(_this->samplerates[_this->srId]); _this->dev->setFrequency(_this->freq); _this->dev->setGain(_this->gain); - _this->dev->writeReg(0, 3 << 24); _this->running = true; spdlog::info("HermesSourceModule '{0}': Start!", _this->name); @@ -131,7 +179,7 @@ private: HermesSourceModule* _this = (HermesSourceModule*)ctx; if (_this->running) { // TODO: Check if dev exists - _this->dev->setFrequency(_this->freq); + _this->dev->setFrequency(freq); } _this->freq = freq; spdlog::info("HermesSourceModule '{0}': Tune: {1}!", _this->name, freq); @@ -142,22 +190,53 @@ private: if (_this->running) { SmGui::BeginDisabled(); } - // TODO: Device selection - if (SmGui::Button("Discover")) { - auto disc = hermes::discover(); - spdlog::warn("Found {0} devices!", disc.size()); + SmGui::FillWidth(); + SmGui::ForceSync(); + if (SmGui::Combo(CONCAT("##_hermes_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { + _this->selectMac(_this->devices.key(_this->devId)); + if (!_this->selectedMac.empty()) { + config.acquire(); + config.conf["device"] = _this->devices.key(_this->devId); + config.release(true); + } + } + + if (SmGui::Combo(CONCAT("##_hermes_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) { + _this->sampleRate = _this->samplerates.key(_this->srId); + core::setInputSampleRate(_this->sampleRate); + if (!_this->selectedMac.empty()) { + config.acquire(); + config.conf["devices"][_this->selectedMac]["samplerate"] = _this->samplerates.key(_this->srId); + config.release(true); + } + } + + SmGui::SameLine(); + SmGui::FillWidth(); + SmGui::ForceSync(); + if (SmGui::Button(CONCAT("Refresh##_hermes_refr_", _this->name))) { + _this->refresh(); + config.acquire(); + std::string mac = config.conf["device"]; + config.release(); + _this->selectMac(mac); } if (_this->running) { SmGui::EndDisabled(); } // TODO: Device parameters - if (SmGui::SliderInt("Gain##hermes_source", &_this->gain, 0, 60)) { - _this->dev->setGain(_this->gain); - } - - if (SmGui::Button("Hermes Test")) { - _this->dev->readReg(hermes::HL_REG_RX1_NCO_FREQ); + SmGui::LeftLabel("LNA Gain"); + SmGui::FillWidth(); + if (SmGui::SliderInt("##hermes_source_lna_gain", &_this->gain, 0, 60)) { + if (_this->running) { + _this->dev->setGain(_this->gain); + } + if (!_this->selectedMac.empty()) { + config.acquire(); + config.conf["devices"][_this->selectedMac]["gain"] = _this->gain; + config.release(true); + } } } @@ -168,14 +247,18 @@ private: double sampleRate; SourceManager::SourceHandler handler; bool running = false; - double freq; + std::string selectedMac = ""; + OptionList devices; + OptionList samplerates; + + double freq; + int devId = 0; + int srId = 0; int gain = 0; std::shared_ptr dev; - std::vector devList; - std::string devListTxt; }; MOD_EXPORT void _INIT_() { diff --git a/source_modules/hermes_source/src/net.cpp b/source_modules/hermes_source/src/net.cpp index c26a1986..76d3e78a 100644 --- a/source_modules/hermes_source/src/net.cpp +++ b/source_modules/hermes_source/src/net.cpp @@ -37,6 +37,7 @@ namespace net { void closeSocket(SockHandle_t sock) { #ifdef _WIN32 + shutdown(sock, SD_BOTH); closesocket(sock); #else shutdown(sock, SHUT_RDWR); @@ -44,19 +45,80 @@ namespace net { #endif } + void setNonblocking(SockHandle_t sock) { +#ifdef _WIN32 + u_long enabled = 1; + ioctlsocket(sock, FIONBIO, &enabled); +#else + fcntl(sock, F_SETFL, O_NONBLOCK); +#endif + } + + // === Address functions === + + Address::Address() { + memset(&addr, 0, sizeof(addr)); + } + + Address::Address(const std::string& host, int port) { + // Initialize WSA if needed + init(); + + // Lookup host + hostent* ent = gethostbyname(host.c_str()); + if (!ent || !ent->h_addr_list[0]) { + throw std::runtime_error("Unknown host"); + } + + // Build address + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = *(uint32_t*)ent->h_addr_list[0]; + addr.sin_port = htons(port); + } + + Address::Address(IP_t ip, int port) { + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(ip); + addr.sin_port = htons(port); + } + + std::string Address::getIPStr() { + char buf[128]; + IP_t ip = getIP(); + sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); + return buf; + } + + IP_t Address::getIP() { + return htonl(addr.sin_addr.s_addr); + } + + void Address::setIP(IP_t ip) { + addr.sin_addr.s_addr = htonl(ip); + } + + int Address::getPort() { + return htons(addr.sin_port); + } + + void Address::setPort(int port) { + addr.sin_port = htons(port); + } + // === Socket functions === - Socket::Socket(SockHandle_t sock, struct sockaddr_in* raddr) { + Socket::Socket(SockHandle_t sock, const Address* raddr) { this->sock = sock; if (raddr) { - this->raddr = (struct sockaddr_in*)malloc(sizeof(sockaddr_in)); - memcpy(this->raddr, raddr, sizeof(sockaddr_in)); + this->raddr = new Address(*raddr); } } Socket::~Socket() { close(); - if (raddr) { free(raddr); } + if (raddr) { delete raddr; } } void Socket::close() { @@ -73,15 +135,15 @@ namespace net { return raddr ? SOCKET_TYPE_UDP : SOCKET_TYPE_TCP; } - int Socket::send(const uint8_t* data, size_t len) { - return sendto(sock, (const char*)data, len, 0, (sockaddr*)raddr, sizeof(sockaddr_in)); + int Socket::send(const uint8_t* data, size_t len, const Address* dest) { + return sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in)); } - int Socket::sendstr(const std::string& str) { - return send((const uint8_t*)str.c_str(), str.length()); + int Socket::sendstr(const std::string& str, const Address* dest) { + return send((const uint8_t*)str.c_str(), str.length(), dest); } - int Socket::recv(uint8_t* data, size_t maxLen, bool forceLen, int timeout) { + int Socket::recv(uint8_t* data, size_t maxLen, bool forceLen, int timeout, Address* dest) { // Create FD set fd_set set; FD_ZERO(&set); @@ -102,7 +164,8 @@ namespace net { } // Receive - int err = ::recv(sock, (char*)&data[read], maxLen - read, 0); + int addrLen = sizeof(sockaddr_in); + int err = ::recvfrom(sock, (char*)&data[read], maxLen - read, 0,(sockaddr*)(dest ? &dest->addr : NULL), dest ? &addrLen : NULL); if (err <= 0 && !WOULD_BLOCK) { close(); return err; @@ -113,7 +176,7 @@ namespace net { return read; } - int Socket::recvline(std::string& str, int maxLen, int timeout) { + int Socket::recvline(std::string& str, int maxLen, int timeout, Address* dest) { // Disallow nonblocking mode if (timeout < 0) { return -1; } @@ -121,7 +184,7 @@ namespace net { int read = 0; while (true) { char c; - int err = recv((uint8_t*)&c, 1, timeout); + int err = recv((uint8_t*)&c, 1, false, timeout, dest); if (err <= 0) { return err; } if (c == '\n') { break; } str += c; @@ -150,7 +213,7 @@ namespace net { return open; } - std::shared_ptr Listener::accept(int timeout) { + std::shared_ptr Listener::accept(Address* dest, int timeout) { // Create FD set fd_set set; FD_ZERO(&set); @@ -162,40 +225,31 @@ namespace net { tv.tv_usec = timeout * 1000; // Wait for data or error - // TODO: Support non-blockign mode - int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL); - if (err <= 0) { return NULL; } + if (timeout != NONBLOCKING) { + int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL); + if (err <= 0) { return NULL; } + } // Accept - SockHandle_t s = ::accept(sock, NULL, 0); - if (!s) { - stop(); + int addrLen = sizeof(sockaddr_in); + SockHandle_t s = ::accept(sock, (sockaddr*)(dest ? &dest->addr : NULL), dest ? &addrLen : NULL); + if ((int)s < 0) { + if (!WOULD_BLOCK) { stop(); } return NULL; } // Enable nonblocking mode -#ifdef _WIN32 - u_long enabled = 1; - ioctlsocket(s, FIONBIO, &enabled); -#else - fcntl(s, F_SETFL, O_NONBLOCK); -#endif + setNonblocking(s); return std::make_shared(s); } // === Creation functions === - std::shared_ptr listen(std::string host, int port) { + std::shared_ptr listen(const Address& addr) { // Init library if needed init(); - // Get host address - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - if (!queryHost((uint32_t*)&addr.sin_addr.s_addr, host)) { return NULL; } - // Create socket SockHandle_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // TODO: Support non-blockign mode @@ -214,7 +268,7 @@ namespace net { #endif // Bind socket to the port - if (bind(s, (sockaddr*)&addr, sizeof(addr))) { + if (bind(s, (sockaddr*)&addr.addr, sizeof(sockaddr_in))) { closeSocket(s); throw std::runtime_error("Could not bind socket"); return NULL; @@ -226,63 +280,51 @@ namespace net { return NULL; } + // Enable nonblocking mode + setNonblocking(s); + // Return listener class return std::make_shared(s); } - std::shared_ptr connect(std::string host, int port) { + std::shared_ptr listen(std::string host, int port) { + return listen(Address(host, port)); + } + + std::shared_ptr connect(const Address& addr) { // Init library if needed init(); - // Get host address - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - if (!queryHost((uint32_t*)&addr.sin_addr.s_addr, host)) { return NULL; } - // Create socket SockHandle_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Connect to server - if (::connect(s, (sockaddr*)&addr, sizeof(addr))) { + if (::connect(s, (sockaddr*)&addr.addr, sizeof(sockaddr_in))) { closeSocket(s); throw std::runtime_error("Could not connect"); return NULL; } // Enable nonblocking mode -#ifdef _WIN32 - u_long enabled = 1; - ioctlsocket(s, FIONBIO, &enabled); -#else - fcntl(s, F_SETFL, O_NONBLOCK); -#endif + setNonblocking(s); // Return socket class return std::make_shared(s); } - std::shared_ptr openudp(std::string rhost, int rport, std::string lhost, int lport) { + std::shared_ptr connect(std::string host, int port) { + return connect(Address(host, port)); + } + + std::shared_ptr openudp(const Address& raddr, const Address& laddr) { // Init library if needed init(); - // Get local address - struct sockaddr_in laddr; - laddr.sin_family = AF_INET; - laddr.sin_port = htons(lport); - if (!queryHost((uint32_t*)&laddr.sin_addr.s_addr, lhost)) { return NULL; } - - // Get remote address - struct sockaddr_in raddr; - raddr.sin_family = AF_INET; - raddr.sin_port = htons(rport); - if (!queryHost((uint32_t*)&raddr.sin_addr.s_addr, rhost)) { return NULL; } - // Create socket SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // Bind socket to local port - if (bind(s, (sockaddr*)&laddr, sizeof(laddr))) { + if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) { closeSocket(s); throw std::runtime_error("Could not bind socket"); return NULL; @@ -291,4 +333,16 @@ namespace net { // Return socket class return std::make_shared(s, &raddr); } + + std::shared_ptr openudp(std::string rhost, int rport, const Address& laddr) { + return openudp(Address(rhost, rport), laddr); + } + + std::shared_ptr openudp(const Address& raddr, std::string lhost, int lport) { + return openudp(raddr, Address(lhost, lport)); + } + + std::shared_ptr openudp(std::string rhost, int rport, std::string lhost, int lport) { + return openudp(Address(rhost, rport), Address(lhost, lport)); + } } \ No newline at end of file diff --git a/source_modules/hermes_source/src/net.h b/source_modules/hermes_source/src/net.h index 4a0cde05..8f9dc9ce 100644 --- a/source_modules/hermes_source/src/net.h +++ b/source_modules/hermes_source/src/net.h @@ -24,6 +24,66 @@ namespace net { #else typedef int SockHandle_t; #endif + typedef uint32_t IP_t; + + class Socket; + class Listener; + + class Address { + friend Socket; + friend Listener; + public: + /** + * Default constructor. Corresponds to 0.0.0.0:0. + */ + Address(); + + /** + * Do not instantiate this class manually. Use the provided functions. + * @param host Hostname or IP. + * @param port TCP/UDP port. + */ + Address(const std::string& host, int port); + + /** + * Do not instantiate this class manually. Use the provided functions. + * @param ip IP in host byte order. + * @param port TCP/UDP port. + */ + Address(IP_t ip, int port); + + /** + * Get the IP address. + * @return IP address in standard string format. + */ + std::string getIPStr(); + + /** + * Get the IP address. + * @return IP address in host byte order. + */ + IP_t getIP(); + + /** + * Set the IP address. + * @param ip IP address in host byte order. + */ + void setIP(IP_t ip); + + /** + * Get the TCP/UDP port. + * @return TCP/UDP port number. + */ + int getPort(); + + /** + * Set the TCP/UDP port. + * @param port TCP/UDP port number. + */ + void setPort(int port); + + struct sockaddr_in addr; + }; enum { NO_TIMEOUT = -1, @@ -37,7 +97,10 @@ namespace net { class Socket { public: - Socket(SockHandle_t sock, struct sockaddr_in* raddr = NULL); + /** + * Do not instantiate this class manually. Use the provided functions. + */ + Socket(SockHandle_t sock, const Address* raddr = NULL); ~Socket(); /** @@ -61,16 +124,18 @@ namespace net { * Send data on socket. * @param data Data to be sent. * @param len Number of bytes to be sent. + * @param dest Destination address. NULL to use the default remote address. * @return Number of bytes sent. */ - int send(const uint8_t* data, size_t len); + int send(const uint8_t* data, size_t len, const Address* dest = NULL); /** - * Send string on socket. Terminating null byte is not sent, include one in the string if you need it. + * Send string on socket. Terminating NULL byte is not sent, include one in the string if you need it. * @param str String to be sent. + * @param dest Destination address. NULL to use the default remote address. * @return Number of bytes sent. */ - int sendstr(const std::string& str); + int sendstr(const std::string& str, const Address* dest = NULL); /** * Receive data from socket. @@ -78,21 +143,23 @@ namespace net { * @param maxLen Maximum number of bytes to read. * @param forceLen Read the maximum number of bytes even if it requires multiple receive operations. * @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed. + * @param dest Destination address. If multiple packets, this will contain the address of the last one. NULL if not used. * @return Number of bytes read. 0 means timed out or closed. -1 means would block or error. */ - int recv(uint8_t* data, size_t maxLen, bool forceLen = false, int timeout = NO_TIMEOUT); + int recv(uint8_t* data, size_t maxLen, bool forceLen = false, int timeout = NO_TIMEOUT, Address* dest = NULL); /** * Receive line from socket. * @param str String to read the data into. * @param maxLen Maximum line length allowed, 0 for no limit. * @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed. + * @param dest Destination address. If multiple packets, this will contain the address of the last one. NULL if not used. * @return Length of the returned string. 0 means timed out or closed. -1 means would block or error. */ - int recvline(std::string& str, int maxLen = 0, int timeout = NO_TIMEOUT); + int recvline(std::string& str, int maxLen = 0, int timeout = NO_TIMEOUT, Address* dest = NULL); private: - struct sockaddr_in* raddr = NULL; + Address* raddr = NULL; SockHandle_t sock; bool open = true; @@ -100,6 +167,9 @@ namespace net { class Listener { public: + /** + * Do not instantiate this class manually. Use the provided functions. + */ Listener(SockHandle_t sock); ~Listener(); @@ -116,10 +186,10 @@ namespace net { /** * Accept connection. - * @param timeout Timeout in milliseconds. 0 means no timeout. - * @return Socket of the connection. NULL means timed out or closed. + * @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed. + * @return Socket of the connection. NULL means timed out, would block or closed. */ - std::shared_ptr accept(int timeout = NO_TIMEOUT); + std::shared_ptr accept(Address* dest = NULL, int timeout = NO_TIMEOUT); private: SockHandle_t sock; @@ -127,29 +197,69 @@ namespace net { }; + /** + * Create TCP listener. + * @param addr Address to listen on. + * @return Listener instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr listen(const Address& addr); + /** * Create TCP listener. * @param host Hostname or IP to listen on ("0.0.0.0" for Any). * @param port Port to listen on. - * @return Listener instance on success, null otherwise. + * @return Listener instance on success, Throws runtime_error otherwise. */ std::shared_ptr listen(std::string host, int port); + /** + * Create TCP connection. + * @param addr Remote address. + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr connect(const Address& addr); + /** * Create TCP connection. * @param host Remote hostname or IP address. * @param port Remote port. - * @return Socket instance on success, null otherwise. + * @return Socket instance on success, Throws runtime_error otherwise. */ - std::shared_ptr connect(std::string host, int port); + std::shared_ptr connect(std::string host, int port); + + /** + * Create UDP socket. + * @param raddr Remote address. + * @param laddr Local address to bind the socket to. + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr openudp(const Address& raddr, const Address& laddr); + + /** + * Create UDP socket. + * @param rhost Remote hostname or IP address. + * @param rport Remote port. + * @param laddr Local address to bind the socket to. + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr openudp(std::string rhost, int rport, const Address& laddr); + + /** + * Create UDP socket. + * @param raddr Remote address. + * @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any). + * @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically). + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0); /** * Create UDP socket. * @param rhost Remote hostname or IP address. * @param rport Remote port. * @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any). - * @param lpost Local port used to bind the socket (optional, 0 to allocate automatically). - * @return Socket instance on success, null otherwise. + * @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically). + * @return Socket instance on success, Throws runtime_error otherwise. */ - std::shared_ptr openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0); + std::shared_ptr openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0); } \ No newline at end of file diff --git a/source_modules/usrp_source/CMakeLists.txt b/source_modules/usrp_source/CMakeLists.txt new file mode 100644 index 00000000..74e7b96a --- /dev/null +++ b/source_modules/usrp_source/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.13) +project(usrp_source) + +file(GLOB SRC "src/*.cpp") + +add_library(usrp_source SHARED ${SRC}) +target_link_libraries(usrp_source PRIVATE sdrpp_core) +set_target_properties(usrp_source PROPERTIES PREFIX "") + +target_include_directories(usrp_source PRIVATE "src/") + +if (MSVC) + target_compile_options(usrp_source PRIVATE /O2 /Ob2 /std:c++17 /EHsc) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(usrp_source PRIVATE -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup) +else () + target_compile_options(usrp_source PRIVATE -O3 -std=c++17) +endif () + +if (MSVC) + # Lib path + target_link_directories(usrp_source PRIVATE "C:/Program Files/PothosSDR/bin/") + + target_include_directories(usrp_source PRIVATE "C:/Program Files/PothosSDR/include/") + + target_link_libraries(usrp_source PRIVATE uhd) +else (MSVC) + find_package(PkgConfig) + + pkg_check_modules(LIBUHD REQUIRED uhd) + + target_include_directories(usrp_source PRIVATE ${LIBUHD_INCLUDE_DIRS}) + target_link_directories(usrp_source PRIVATE ${LIBUHD_LIBRARY_DIRS}) + target_link_libraries(usrp_source PRIVATE ${LIBUHD_LIBRARIES}) +endif () + +# Install directives +install(TARGETS usrp_source DESTINATION lib/sdrpp/plugins) \ No newline at end of file diff --git a/source_modules/usrp_source/src/main.cpp b/source_modules/usrp_source/src/main.cpp new file mode 100644 index 00000000..00a6a938 --- /dev/null +++ b/source_modules/usrp_source/src/main.cpp @@ -0,0 +1,429 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +SDRPP_MOD_INFO{ + /* Name: */ "usrp_source", + /* Description: */ "USRP source module for SDR++", + /* Author: */ "Ryzerth", + /* Version: */ 0, 1, 0, + /* Max instances */ 1 +}; + +ConfigManager config; + +class USRPSourceModule : public ModuleManager::Instance { +public: + USRPSourceModule(std::string name) { + this->name = name; + + sampleRate = 8000000.0; + // TODO: REMOVE + samplerates.define(8000000, "8MHz", 8000000.0); + + handler.ctx = this; + handler.selectHandler = menuSelected; + handler.deselectHandler = menuDeselected; + handler.menuHandler = menuHandler; + handler.startHandler = start; + handler.stopHandler = stop; + handler.tuneHandler = tune; + handler.stream = &stream; + + // List devices + refresh(); + + // Select device + config.acquire(); + selectedSer = config.conf["device"]; + config.release(); + select(selectedSer); + + sigpath::sourceManager.registerSource("USRP", &handler); + } + + ~USRPSourceModule() { + stop(this); + sigpath::sourceManager.unregisterSource("USRP"); + } + + void postInit() {} + + enum AGCMode { + AGC_MODE_OFF, + AGC_MODE_LOW, + AGC_MODE_HIGG + }; + + void enable() { + enabled = true; + } + + void disable() { + enabled = false; + } + + bool isEnabled() { + return enabled; + } + + void refresh() { + devices.clear(); + uhd::device_addr_t hint; + uhd::device_addrs_t devList = uhd::device::find(hint); + + char buf[1024]; + for (const auto& devAddr : devList) { + std::string serial = devAddr["serial"]; + std::string model = devAddr.has_key("product") ? devAddr["product"] : devAddr["type"]; + sprintf(buf, "USRP %s [%s]", model.c_str(), serial.c_str()); + devices.define(serial, buf, devAddr); + } + } + + void select(std::string serial) { + // If no device, give up + if (!devices.size()) { + selectedSer.clear(); + return; + } + + // If the wanted serial is not available, select first + if (!devices.keyExists(serial)) { + select(devices.key(0)); + return; + } + + // Update selection + selectedSer = serial; + devId = devices.keyId(serial); + + // Make device + auto dev = uhd::usrp::multi_usrp::make(devices[devId]); + + // List subdevices + char buf[1024]; + channels.clear(); + auto subdevs = dev->get_rx_subdev_spec(); + for (int i = 0; i < subdevs.size(); i++) { + std::string slot = subdevs[i].db_name; + sprintf(buf, "%s [%s]", dev->get_rx_subdev_name(i), slot.c_str()); + channels.define(buf, buf, slot); + } + + // Select channel + std::string chan = ""; + config.acquire(); + if (config.conf["devices"][selectedSer].contains("channel")) { + chan = config.conf["devices"][selectedSer]["channel"]; + } + config.release(); + selectChannel(dev, chan); + } + + void selectChannel(uhd::usrp::multi_usrp::sptr dev, std::string chan) { + // If wanted channel is not available, select first + if (!channels.keyExists(chan)) { + selectChannel(dev, channels.key(0)); + return; + } + + // Update selection + selectedChan = chan; + chanId = channels.keyId(chan); + + // List samplerates + samplerates.clear(); + auto srList = dev->get_rx_rates(chanId); + for (auto& l : srList) { + if (l.step() == 0.0 || l.start() == l.stop()) { + samplerates.define(l.start(), getBandwdithScaled(l.start()), l.start()); + } + else { + for (double f = l.start(); f <= l.stop(); f += l.step()) { + samplerates.define(f, getBandwdithScaled(f), f); + } + } + } + + // List antennas + antennas.clear(); + auto ants = dev->get_rx_antennas(chanId); + for (const auto& a : ants) { + antennas.define(a,a,a); + } + + // Get gain range + gainRange = dev->get_rx_gain_range(chanId)[0]; + + // Load settings + srId = 0; + antId = 0; + gain = gainRange.start(); + config.acquire(); + if (config.conf["devices"][selectedSer].contains("channels") && config.conf["devices"][selectedSer]["channels"].contains(selectedChan)) { + auto cconf = config.conf["devices"][selectedSer]["channels"][selectedChan]; + if (cconf.contains("samplerate")) { + int sr = cconf["samplerate"]; + if (samplerates.keyExists(sr)) { srId = samplerates.keyId(sr); } + } + if (cconf.contains("antenna")) { + std::string ant = cconf["antenna"]; + if (antennas.keyExists(ant)) { antId = antennas.keyId(ant); } + } + if (cconf.contains("gain")) { + gain = cconf["gain"]; + gain = std::clamp(gain, gainRange.start(), gainRange.stop()); + } + } + config.release(); + + // Apply samplerate + sampleRate = samplerates.key(srId); + core::setInputSampleRate(sampleRate); + } + +private: + std::string getBandwdithScaled(double bw) { + char buf[1024]; + // if (bw >= 1000000.0) { + // sprintf(buf, "%.1lfMHz", bw / 1000000.0); + // } + // else if (bw >= 1000.0) { + // sprintf(buf, "%.1lfKHz", bw / 1000.0); + // } + // else { + sprintf(buf, "%.1lfHz", bw); + //} + return std::string(buf); + } + + static void menuSelected(void* ctx) { + USRPSourceModule* _this = (USRPSourceModule*)ctx; + core::setInputSampleRate(_this->sampleRate); + spdlog::info("USRPSourceModule '{0}': Menu Select!", _this->name); + } + + static void menuDeselected(void* ctx) { + USRPSourceModule* _this = (USRPSourceModule*)ctx; + spdlog::info("USRPSourceModule '{0}': Menu Deselect!", _this->name); + } + + static void start(void* ctx) { + USRPSourceModule* _this = (USRPSourceModule*)ctx; + if (_this->running) { return; } + if (_this->selectedSer.empty()) { return; } + + _this->dev = uhd::usrp::multi_usrp::make(_this->devices[_this->devId]); + + _this->dev->set_rx_rate(_this->sampleRate, _this->chanId); + _this->dev->set_rx_antenna(_this->antennas.key(_this->antId), _this->chanId); + _this->dev->set_rx_gain(_this->gain, _this->chanId); + _this->dev->set_rx_freq(_this->freq, _this->chanId); + + uhd::stream_args_t sargs; + sargs.channels.clear(); + sargs.channels.push_back(_this->chanId); + sargs.cpu_format = "fc32"; + sargs.otw_format = "sc16"; + _this->streamer = _this->dev->get_rx_stream(sargs); + _this->streamer->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + + _this->stream.clearWriteStop(); + _this->workerThread = std::thread(&USRPSourceModule::worker, _this); + + _this->running = true; + spdlog::info("USRPSourceModule '{0}': Start!", _this->name); + } + + static void stop(void* ctx) { + USRPSourceModule* _this = (USRPSourceModule*)ctx; + if (!_this->running) { return; } + _this->running = false; + + _this->stream.stopWriter(); + _this->streamer->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + if (_this->workerThread.joinable()) { _this->workerThread.join(); } + _this->stream.clearWriteStop(); + + _this->streamer.reset(); + _this->dev.reset(); + + spdlog::info("USRPSourceModule '{0}': Stop!", _this->name); + } + + static void tune(double freq, void* ctx) { + USRPSourceModule* _this = (USRPSourceModule*)ctx; + if (_this->running) { + _this->dev->set_rx_freq(freq, _this->chanId); + } + _this->freq = freq; + spdlog::info("USRPSourceModule '{0}': Tune: {1}!", _this->name, freq); + } + + static void menuHandler(void* ctx) { + USRPSourceModule* _this = (USRPSourceModule*)ctx; + + if (_this->running) { SmGui::BeginDisabled(); } + + SmGui::FillWidth(); + SmGui::ForceSync(); + if (SmGui::Combo(CONCAT("##_usrp_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { + _this->select(_this->devices.key(_this->devId)); + if (!_this->selectedSer.empty()) { + config.acquire(); + config.conf["device"] = _this->devices.key(_this->devId); + config.release(true); + } + } + + if (SmGui::Combo(CONCAT("##_usrp_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) { + _this->sampleRate = _this->samplerates.key(_this->srId); + core::setInputSampleRate(_this->sampleRate); + if (!_this->selectedSer.empty()) { + config.acquire(); + config.conf["devices"][_this->selectedSer]["channels"][_this->selectedChan]["samplerate"] = _this->samplerates.key(_this->srId); + config.release(true); + } + } + + SmGui::SameLine(); + SmGui::FillWidth(); + SmGui::ForceSync(); + if (SmGui::Button(CONCAT("Refresh##_usrp_refr_", _this->name))) { + _this->refresh(); + config.acquire(); + std::string ser = config.conf["device"]; + config.release(); + _this->select(ser); + } + + SmGui::LeftLabel("Channel"); + SmGui::FillWidth(); + SmGui::ForceSync(); + if (SmGui::Combo(CONCAT("##_usrp_ch_sel_", _this->name), &_this->chanId, _this->channels.txt)) { + if (!_this->selectedSer.empty()) { + config.acquire(); + config.conf["devices"][_this->selectedSer]["channel"] = _this->channels.key(_this->chanId); + config.release(true); + } + _this->select(_this->devices.key(_this->devId)); + } + + if (_this->running) { SmGui::EndDisabled(); } + + SmGui::LeftLabel("Antenna"); + SmGui::FillWidth(); + if (SmGui::Combo(CONCAT("##_usrp_ant_sel_", _this->name), &_this->antId, _this->antennas.txt)) { + if (_this->running) { + _this->dev->set_rx_antenna(_this->antennas.key(_this->antId), _this->chanId); + } + if (!_this->selectedSer.empty() && !_this->selectedChan.empty()) { + config.acquire(); + config.conf["devices"][_this->selectedSer]["channels"][_this->selectedChan]["antenna"] = _this->antennas.key(_this->antId); + config.release(true); + } + } + + SmGui::LeftLabel("Gain"); + SmGui::FillWidth(); + if (SmGui::SliderFloatWithSteps(CONCAT("##_usrp_gain_", _this->name), &_this->gain, _this->gainRange.start(), _this->gainRange.stop(), _this->gainRange.step(), SmGui::FMT_STR_FLOAT_DB_ONE_DECIMAL)) { + if (_this->running) { + _this->dev->set_rx_gain(_this->gain, _this->chanId); + } + if (!_this->selectedSer.empty() && !_this->selectedChan.empty()) { + config.acquire(); + config.conf["devices"][_this->selectedSer]["channels"][_this->selectedChan]["gain"] = _this->gain; + config.release(true); + } + } + } + + uint32_t floor2(uint32_t val) { + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + return val - (val >> 1); + } + + void worker() { + // TODO: Select a better buffer size that will avoid bad timing + int bufferSize = sampleRate / 200; + while (true) { + uhd::rx_metadata_t meta; + void* ptr[] = { stream.writeBuf }; + uhd::rx_streamer::buffs_type buffers(ptr, 1); + int len = streamer->recv(stream.writeBuf, bufferSize, meta, 1.0); + if (len < 0) { break; } + if (len != bufferSize) { + printf("%d\n", len); + } + if (len) { + if (!stream.swap(len)) { break; } + } + } + } + + std::string name; + bool enabled = true; + dsp::stream stream; + double sampleRate; + SourceManager::SourceHandler handler; + bool running = false; + double freq; + int devId = 0; + int chanId = 0; + int srId = 0; + int antId = 0; + std::string selectedSer = ""; + std::string selectedChan = ""; + float gain = 0.0f; + + OptionList devices; + OptionList channels; + OptionList samplerates; + OptionList antennas; + uhd::range_t gainRange; + + uhd::usrp::multi_usrp::sptr dev; + uhd::rx_streamer::sptr streamer; + + std::thread workerThread; + +}; + +MOD_EXPORT void _INIT_() { + json def = json({}); + def["devices"] = json({}); + def["device"] = ""; + config.setPath(core::args["root"].s() + "/usrp_config.json"); + config.load(def); + config.enableAutoSave(); +} + +MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { + return new USRPSourceModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { + delete (USRPSourceModule*)instance; +} + +MOD_EXPORT void _END_() { + config.disableAutoSave(); + config.save(); +} \ No newline at end of file