diff --git a/CMakeLists.txt b/CMakeLists.txt index 34b3e419..fcb5827e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory("core") add_subdirectory("radio") add_subdirectory("recorder") add_subdirectory("soapy_source") +add_subdirectory("airspyhf_source") #add_subdirectory("file_source") add_subdirectory("rtl_tcp_source") add_subdirectory("audio_sink") diff --git a/airspy_hf_source/CMakeLists.txt b/airspy_hf_source/CMakeLists.txt deleted file mode 100644 index adf4c72e..00000000 --- a/airspy_hf_source/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(airspy_hf_source) - -if (MSVC) - set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") -else() - set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") -endif (MSVC) - -include_directories("src/") - -file(GLOB SRC "src/*.cpp") - -add_library(airspy_hf_source SHARED ${SRC}) -target_link_libraries(airspy_hf_source PRIVATE sdrpp_core) -set_target_properties(airspy_hf_source PROPERTIES PREFIX "") - -if(WIN32) - target_link_libraries(airspy_hf_source PRIVATE wsock32 ws2_32) -endif() \ No newline at end of file diff --git a/airspy_hf_source/src/main.cpp b/airspy_hf_source/src/main.cpp deleted file mode 100644 index 98c5bfbe..00000000 --- a/airspy_hf_source/src/main.cpp +++ /dev/null @@ -1,192 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#define CONCAT(a, b) ((std::string(a) + b).c_str()) - -SDRPP_MOD_INFO { - /* Name: */ "airspy_hf_source", - /* Description: */ "Airspy HF source module for SDR++", - /* Author: */ "Ryzerth", - /* Version: */ 0, 1, 0, - /* Max instances */ 1 -}; - -class AirspyHFSourceModule : public ModuleManager::Instance { -public: - AirspyHFSourceModule(std::string name) { - this->name = name; - - sampleRate = 2560000.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; - sigpath::sourceManager.registerSource("Airspy HF+", &handler); - } - - ~AirspyHFSourceModule() { - - } - - void enable() { - enabled = true; - } - - void disable() { - enabled = false; - } - - bool isEnabled() { - return enabled; - } - -private: - static void menuSelected(void* ctx) { - AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; - core::setInputSampleRate(_this->sampleRate); - spdlog::info("AirspyHFSourceModule '{0}': Menu Select!", _this->name); - } - - static void menuDeselected(void* ctx) { - AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; - spdlog::info("AirspyHFSourceModule '{0}': Menu Deselect!", _this->name); - } - - static void start(void* ctx) { - AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; - if (_this->running) { - return; - } - // DO START HERE - _this->running = true; - _this->workerThread = std::thread(worker, _this); - spdlog::info("AirspyHFSourceModule '{0}': Start!", _this->name); - } - - static void stop(void* ctx) { - AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; - if (!_this->running) { - return; - } - // DO STOP HERE - _this->running = false; - _this->stream.stopWriter(); - _this->workerThread.join(); - _this->stream.clearWriteStop(); - spdlog::info("AirspyHFSourceModule '{0}': Stop!", _this->name); - } - - static void tune(double freq, void* ctx) { - AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; - if (_this->running) { - _this->client.setFrequency(freq); - } - _this->freq = freq; - spdlog::info("AirspyHFSourceModule '{0}': Tune: {1}!", _this->name, freq); - } - - static void menuHandler(void* ctx) { - AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; - float menuWidth = ImGui::GetContentRegionAvailWidth(); - float portWidth = ImGui::CalcTextSize("00000").x + 20; - - ImGui::SetNextItemWidth(menuWidth - portWidth); - ImGui::InputText(CONCAT("##_ip_select_", _this->name), _this->ip, 1024); - ImGui::SameLine(); - ImGui::SetNextItemWidth(portWidth); - ImGui::InputInt(CONCAT("##_port_select_", _this->name), &_this->port, 0); - - ImGui::SetNextItemWidth(ImGui::CalcTextSize("OOOOOOOOOO").x); - if (ImGui::Combo("Direct sampling", &_this->directSamplingMode, "Disabled\0I branch\0Q branch\0")) { - if (_this->running) { - _this->client.setDirectSampling(_this->directSamplingMode); - } - } - - if (ImGui::Checkbox("RTL AGC", &_this->rtlAGC)) { - if (_this->running) { - _this->client.setAGCMode(_this->rtlAGC); - } - } - - if (ImGui::Checkbox("Tuner AGC", &_this->tunerAGC)) { - if (_this->running) { - _this->client.setGainMode(!_this->tunerAGC); - if (!_this->tunerAGC) { - _this->client.setGainIndex(_this->gain); - } - } - } - - if (_this->tunerAGC) { style::beginDisabled(); } - ImGui::SetNextItemWidth(menuWidth); - if (ImGui::SliderInt(CONCAT("##_gain_select_", _this->name), &_this->gain, 0, 29, "")) { - if (_this->running) { - _this->client.setGainIndex(_this->gain); - } - } - if (_this->tunerAGC) { style::endDisabled(); } - } - - static void worker(void* ctx) { - AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; - int blockSize = _this->sampleRate / 200.0; - uint8_t* inBuf = new uint8_t[blockSize * 2]; - - while (true) { - // Read samples here - _this->client.receiveData(inBuf, blockSize * 2); - if (_this->stream.aquire() < 0) { break; } - for (int i = 0; i < blockSize; i++) { - _this->stream.data[i].q = ((double)inBuf[i * 2] - 128.0) / 128.0; - _this->stream.data[i].i = ((double)inBuf[(i * 2) + 1] - 128.0) / 128.0; - } - _this->stream.write(blockSize); - } - - delete[] inBuf; - } - - std::string name; - bool enabled = true; - dsp::stream stream; - double sampleRate; - SourceManager::SourceHandler handler; - std::thread workerThread; - RTLTCPClient client; - bool running = false; - double freq; - char ip[1024] = "localhost"; - int port = 1234; - int gain = 0; - bool rtlAGC = false; - bool tunerAGC = false; - int directSamplingMode = 0; -}; - -MOD_EXPORT void _INIT_() { - // Do your one time init here -} - -MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { - return new AirspyHFSourceModule(name); -} - -MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { - delete (AirspyHFSourceModule*)instance; -} - -MOD_EXPORT void _END_() { - // Do your one shutdown here -} \ No newline at end of file diff --git a/airspyhf_source/CMakeLists.txt b/airspyhf_source/CMakeLists.txt new file mode 100644 index 00000000..8c197903 --- /dev/null +++ b/airspyhf_source/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.13) +project(airspyhf_source) + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") +else() + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") +endif (MSVC) + +include_directories("src/") + +file(GLOB SRC "src/*.cpp") + +add_library(airspyhf_source SHARED ${SRC}) +target_link_libraries(airspyhf_source PRIVATE sdrpp_core) +set_target_properties(airspyhf_source PROPERTIES PREFIX "") + +if (MSVC) + # Lib path + target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/") + + target_link_libraries(airspyhf_source PUBLIC airspyhf) +else (MSVC) + find_package(PkgConfig) + + pkg_check_modules(SOAPY REQUIRED airspyhf) + + target_include_directories(airspyhf_source PUBLIC ${AIRSPYHF_INCLUDE_DIRS}) + target_link_directories(airspyhf_source PUBLIC ${AIRSPYHF_LIBRARY_DIRS}) + target_link_libraries(airspyhf_source PUBLIC ${AIRSPYHF_LIBRARIES}) +endif (MSVC) \ No newline at end of file diff --git a/airspyhf_source/src/main.cpp b/airspyhf_source/src/main.cpp new file mode 100644 index 00000000..ad8137c1 --- /dev/null +++ b/airspyhf_source/src/main.cpp @@ -0,0 +1,301 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +SDRPP_MOD_INFO { + /* Name: */ "airspyhf_source", + /* Description: */ "Airspy HF+ source module for SDR++", + /* Author: */ "Ryzerth", + /* Version: */ 0, 1, 0, + /* Max instances */ 1 +}; + +const char* AGG_MODES_STR = "Off\0Low\0High\0"; + +class AirspyHFSourceModule : public ModuleManager::Instance { +public: + AirspyHFSourceModule(std::string name) { + this->name = name; + + sampleRate = 768000.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; + + refresh(); + selectFirst(); + + sigpath::sourceManager.registerSource("Airspy HF+", &handler); + } + + ~AirspyHFSourceModule() { + + } + + 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() { + devList.clear(); + devListTxt = ""; + + uint64_t serials[256]; + int n = airspyhf_list_devices(serials, 256); + + char buf[1024]; + for (int i = 0; i < n; i++) { + sprintf(buf, "%016" PRIX64, serials[i]); + devList.push_back(serials[i]); + devListTxt += buf; + devListTxt += '\0'; + } + } + + void selectFirst() { + if (devList.size() != 0) { + selectBySerial(devList[0]); + } + } + + void selectByString(std::string serial) { + char buf[1024]; + for (int i = 0; i < devList.size(); i++) { + sprintf(buf, "%016" PRIX64, devList[i]); + std::string str = buf; + if (serial == str) { + selectBySerial(devList[i]); + break; + } + } + } + + void selectBySerial(uint64_t serial) { + selectedSerial = serial; + airspyhf_device_t* dev; + int err = airspyhf_open_sn(&dev, selectedSerial); + if (err != 0) { + char buf[1024]; + sprintf(buf, "%016" PRIX64, selectedSerial); + spdlog::error("Could not open Airspy HF+ {0}", buf); + return; + } + + uint32_t sampleRates[256]; + airspyhf_get_samplerates(dev, sampleRates, 0); + int n = sampleRates[0]; + airspyhf_get_samplerates(dev, sampleRates, n); + char buf[1024]; + for (int i = 0; i < n; i++) { + sampleRateList.push_back(sampleRates[i]); + sprintf(buf, "%d", sampleRates[i]); + sampleRateListTxt += buf; + sampleRateListTxt += '\0'; + } + + blockSize = airspyhf_get_output_size(dev); + spdlog::info("AirspyHF block size {0}", blockSize); + + srId = 0; + + airspyhf_close(dev); + } + +private: + static void menuSelected(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + core::setInputSampleRate(_this->sampleRate); + spdlog::info("AirspyHFSourceModule '{0}': Menu Select!", _this->name); + } + + static void menuDeselected(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + spdlog::info("AirspyHFSourceModule '{0}': Menu Deselect!", _this->name); + } + + static void start(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + if (_this->running) { + return; + } + if (_this->selectedSerial == 0) { + spdlog::error("Tried to start AirspyHF+ source with null serial"); + return; + } + + int err = airspyhf_open_sn(&_this->openDev, _this->selectedSerial); + if (err != 0) { + char buf[1024]; + sprintf(buf, "%016" PRIX64, _this->selectedSerial); + spdlog::error("Could not open Airspy HF+ {0}", buf); + return; + } + + spdlog::warn("{0}", _this->sampleRateList[_this->srId]); + + airspyhf_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]); + airspyhf_set_freq(_this->openDev, _this->freq); + airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0)); + if (_this->agcMode > 0) { + airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1); + } + airspyhf_set_hf_att(_this->openDev, _this->atten / 6); + airspyhf_set_hf_lna(_this->openDev, _this->hfLNA); + + airspyhf_start(_this->openDev, callback, _this); + + _this->running = true; + spdlog::info("AirspyHFSourceModule '{0}': Start!", _this->name); + } + + static void stop(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + if (!_this->running) { + return; + } + _this->running = false; + _this->stream.stopWriter(); + airspyhf_close(_this->openDev); + _this->stream.clearWriteStop(); + spdlog::info("AirspyHFSourceModule '{0}': Stop!", _this->name); + } + + static void tune(double freq, void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + if (_this->running) { + airspyhf_set_freq(_this->openDev, freq); + } + _this->freq = freq; + spdlog::info("AirspyHFSourceModule '{0}': Tune: {1}!", _this->name, freq); + } + + static void menuHandler(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + if (_this->running) { style::beginDisabled(); } + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) { + _this->selectBySerial(_this->devList[_this->devId]); + } + + if (ImGui::Combo(CONCAT("##_airspyhf_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) { + _this->sampleRate = _this->sampleRateList[_this->srId]; + core::setInputSampleRate(_this->sampleRate); + } + + ImGui::SameLine(); + float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX(); + if (ImGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) { + _this->refresh(); + _this->selectFirst(); + } + + if (_this->running) { style::endDisabled(); } + + ImGui::Text("AGC Mode"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); + if (ImGui::Combo(CONCAT("##_airspyhf_agc_", _this->name), &_this->agcMode, AGG_MODES_STR)) { + if (_this->running) { + airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0)); + if (_this->agcMode > 0) { + airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1); + } + } + } + + ImGui::Text("HF LNA"); + ImGui::SameLine(); + if (ImGui::Checkbox(CONCAT("##_airspyhf_lna_", _this->name), &_this->hfLNA)) { + if (_this->running) { + airspyhf_set_hf_lna(_this->openDev, _this->hfLNA); + } + } + + ImGui::Text("Attenuation"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); + if (ImGui::SliderInt(CONCAT("##_airspyhf_attn_", _this->name), &_this->atten, 0, 48, "%d dB")) { + _this->atten = (_this->atten / 6) * 6; + if (_this->running) { + airspyhf_set_hf_att(_this->openDev, _this->atten / 6); + } + } + } + + static int callback(airspyhf_transfer_t* transfer) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)transfer->ctx; + if (_this->stream.aquire() < 0) { + return -1; + } + memcpy(_this->stream.data, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t)); + _this->stream.write(transfer->sample_count); + return 0; + } + + std::string name; + airspyhf_device_t* openDev; + bool enabled = true; + int blockSize = 0; + dsp::stream stream; + double sampleRate; + SourceManager::SourceHandler handler; + bool running = false; + double freq; + uint64_t selectedSerial = 0; + int devId = 0; + int srId = 0; + int agcMode = AGC_MODE_OFF; + bool hfLNA = false; + int atten = 0; + + std::vector devList; + std::string devListTxt; + std::vector sampleRateList; + std::string sampleRateListTxt; +}; + +MOD_EXPORT void _INIT_() { + // Do your one time init here +} + +MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { + return new AirspyHFSourceModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { + delete (AirspyHFSourceModule*)instance; +} + +MOD_EXPORT void _END_() { + // Do your one shutdown here +} \ No newline at end of file diff --git a/core/src/options.cpp b/core/src/options.cpp new file mode 100644 index 00000000..53b8ab4d --- /dev/null +++ b/core/src/options.cpp @@ -0,0 +1,7 @@ +#include + +namespace options { + void parse(char** argv, int argc) { + + } +} \ No newline at end of file diff --git a/core/src/options.h b/core/src/options.h new file mode 100644 index 00000000..b8087748 --- /dev/null +++ b/core/src/options.h @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace options { + struct CMDLineOptions { + std::string root; + bool help; + }; + + CMDLineOptions opts; + + void parse(char** argv, int argc); +} diff --git a/root_dev/config.json b/root_dev/config.json index 45c143f0..f78641d5 100644 --- a/root_dev/config.json +++ b/root_dev/config.json @@ -3,9 +3,9 @@ "bandPlanEnabled": true, "centerTuning": false, "fftHeight": 300, - "frequency": 100100000, - "max": 0.0, - "maximized": true, + "frequency": 7375000, + "max": -20.588237762451172, + "maximized": false, "menuOrder": [ "Source", "Radio", @@ -17,8 +17,9 @@ "Display" ], "menuWidth": 300, - "min": -61.764705657958984, + "min": -54.41176986694336, "moduleInstances": { + "AirspyHF+ Source": "airspyhf_source", "Audio Sink": "audio_sink", "PlutoSDR Source": "plutosdr_source", "RTL-TCP Source": "rtl_tcp_source", @@ -27,21 +28,22 @@ "SoapySDR Source": "soapy_source" }, "modules": [ - "./radio/RelWithDebInfo/radio.dll", - "./recorder/RelWithDebInfo/recorder.dll", - "./soapy_source/RelWithDebInfo/soapy_source.dll", - "./rtl_tcp_source/RelWithDebInfo/rtl_tcp_source.dll", - "./audio_sink/RelWithDebInfo/audio_sink.dll", - "./plutosdr_source/RelWithDebInfo/plutosdr_source.dll" + "./radio/Release/radio.dll", + "./recorder/Release/recorder.dll", + "./soapy_source/Release/soapy_source.dll", + "./airspyhf_source/Release/airspyhf_source.dll", + "./rtl_tcp_source/Release/rtl_tcp_source.dll", + "./audio_sink/Release/audio_sink.dll", + "./plutosdr_source/Release/plutosdr_source.dll" ], "offset": 0.0, "showWaterfall": true, - "source": "PlutoSDR", + "source": "Airspy HF+", "streams": { "Radio": { "muted": false, "sink": "Audio", - "volume": 0.2908163368701935 + "volume": 0.12755101919174194 }, "Radio 1": { "muted": false, diff --git a/root_dev/radio_config.json b/root_dev/radio_config.json index 85b3874e..a91fcc6b 100644 --- a/root_dev/radio_config.json +++ b/root_dev/radio_config.json @@ -16,7 +16,8 @@ }, "FM": { "bandwidth": 12500.0, - "snapInterval": 10000.0 + "snapInterval": 10000.0, + "squelchLevel": -47.474998474121094 }, "LSB": { "bandwidth": 3000.0, @@ -35,7 +36,7 @@ "snapInterval": 100000.0, "squelchLevel": -100.0 }, - "selectedDemodId": 1 + "selectedDemodId": 2 }, "Radio 1": { "AM": { diff --git a/root_dev/soapy_source_config.json b/root_dev/soapy_source_config.json index 1e46a6fa..dc359c3d 100644 --- a/root_dev/soapy_source_config.json +++ b/root_dev/soapy_source_config.json @@ -20,6 +20,7 @@ "sampleRate": 96000.0 }, "Default Device": { + "agc": false, "sampleRate": 192000.0 }, "Generic RTL2832U OEM :: 00000001": {