mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 08:58:13 +01:00 
			
		
		
		
	Merge pull request #1049 from AlexandreRouma/master
Merging back changes
This commit is contained in:
		| @@ -17,24 +17,25 @@ option(OPT_BUILD_FILE_SOURCE "Wav file source" ON) | ||||
| option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON) | ||||
| option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF) | ||||
| option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF) | ||||
| option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON) | ||||
| option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON) | ||||
| option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libsdrplay)" OFF) | ||||
| option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" ON) | ||||
| option(OPT_BUILD_SPECTRAN_SOURCE "Build Spectran Source Module (Dependencies: Aaronia RTSA Suite)" OFF) | ||||
| option(OPT_BUILD_SPECTRAN_HTTP_SOURCE "Build Spectran HTTP Source Module (no dependencies required)" 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) | ||||
| option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON) | ||||
| option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF) | ||||
| option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Dependencies: portaudio)" OFF) | ||||
| option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF) | ||||
|  | ||||
| # Decoders | ||||
| option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF) | ||||
| @@ -141,9 +142,13 @@ if (OPT_BUILD_LIMESDR_SOURCE) | ||||
| add_subdirectory("source_modules/limesdr_source") | ||||
| endif (OPT_BUILD_LIMESDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_SDRPP_SERVER_SOURCE) | ||||
| add_subdirectory("source_modules/sdrpp_server_source") | ||||
| endif (OPT_BUILD_SDRPP_SERVER_SOURCE) | ||||
| if (OPT_BUILD_PERSEUS_SOURCE) | ||||
| add_subdirectory("source_modules/perseus_source") | ||||
| endif (OPT_BUILD_PERSEUS_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
| add_subdirectory("source_modules/plutosdr_source") | ||||
| endif (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_RFSPACE_SOURCE) | ||||
| add_subdirectory("source_modules/rfspace_source") | ||||
| @@ -157,6 +162,10 @@ if (OPT_BUILD_RTL_TCP_SOURCE) | ||||
| add_subdirectory("source_modules/rtl_tcp_source") | ||||
| endif (OPT_BUILD_RTL_TCP_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_SDRPP_SERVER_SOURCE) | ||||
| add_subdirectory("source_modules/sdrpp_server_source") | ||||
| endif (OPT_BUILD_SDRPP_SERVER_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_SDRPLAY_SOURCE) | ||||
| add_subdirectory("source_modules/sdrplay_source") | ||||
| endif (OPT_BUILD_SDRPLAY_SOURCE) | ||||
| @@ -177,10 +186,6 @@ if (OPT_BUILD_SPYSERVER_SOURCE) | ||||
| add_subdirectory("source_modules/spyserver_source") | ||||
| endif (OPT_BUILD_SPYSERVER_SOURCE) | ||||
|  | ||||
| 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) | ||||
|   | ||||
							
								
								
									
										52
									
								
								core/src/utils/new_event.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								core/src/utils/new_event.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| #pragma once | ||||
| #include <functional> | ||||
| #include <stdexcept> | ||||
| #include <mutex> | ||||
| #include <map> | ||||
|  | ||||
| typedef int HandlerID; | ||||
|  | ||||
| template <typename... Args> | ||||
| class NewEvent { | ||||
| public: | ||||
|     using Handler = std::function<void(Args...)>; | ||||
|  | ||||
|     HandlerID bind(const Handler& handler) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         HandlerID id = genID(); | ||||
|         handlers[id] = handler; | ||||
|         return id; | ||||
|     } | ||||
|  | ||||
|     template<typename MHandler, class T> | ||||
|     HandlerID bind(MHandler handler, T* ctx) { | ||||
|         return bind([=](Args... args){ | ||||
|             (ctx->*handler)(args...); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     void unbind(HandlerID id) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         if (handlers.find(id) == handlers.end()) { | ||||
|             throw std::runtime_error("Could not unbind handler, unknown ID"); | ||||
|         } | ||||
|         handlers.erase(id); | ||||
|     } | ||||
|  | ||||
|     void operator()(Args... args) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         for (const auto& [desc, handler] : handlers) { | ||||
|             handler(args...); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     HandlerID genID() { | ||||
|         int id; | ||||
|         for (id = 1; handlers.find(id) != handlers.end(); id++); | ||||
|         return id; | ||||
|     } | ||||
|  | ||||
|     std::map<HandlerID, Handler> handlers; | ||||
|     std::mutex mtx; | ||||
| }; | ||||
| @@ -62,6 +62,7 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/decoder_module | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/discord_integration/discord_integration.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/frequency_manager/frequency_manager.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/recorder/recorder.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_client/rigctl_client.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_server/rigctl_server.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/scanner/scanner.dylib | ||||
|  | ||||
|   | ||||
							
								
								
									
										11
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								readme.md
									
									
									
									
									
								
							| @@ -329,11 +329,12 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| |----------------------|------------|-------------------|--------------------------------|:---------------:|:-----------------------:|:---------------------------:| | ||||
| | airspy_source        | Working    | libairspy         | OPT_BUILD_AIRSPY_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | airspyhf_source      | Working    | libairspyhf       | OPT_BUILD_AIRSPYHF_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | bladerf_source       | Working    | libbladeRF        | OPT_BUILD_BLADERF_SOURCE       | ⛔              | ⚠️ (not Debian Buster) | ✅                         | | ||||
| | bladerf_source       | Working    | libbladeRF        | OPT_BUILD_BLADERF_SOURCE       | ⛔              | ✅ (not Debian Buster) | ✅                         | | ||||
| | file_source          | Working    | -                 | OPT_BUILD_FILE_SOURCE          | ✅              | ✅                     | ✅                         | | ||||
| | hackrf_source        | Working    | libhackrf         | OPT_BUILD_HACKRF_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | hermes_source        | Beta       | -                 | OPT_BUILD_HERMES_SOURCE        | ✅              | ✅                     | ⛔                         | | ||||
| | hermes_source        | Beta       | -                 | OPT_BUILD_HERMES_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | limesdr_source       | Working    | liblimesuite      | OPT_BUILD_LIMESDR_SOURCE       | ⛔              | ✅                     | ✅                         | | ||||
| | perseus_source       | Beta       | libperseus-sdr    | OPT_BUILD_PERSEUS_SOURCE       | ⛔              | ⛔                     | ⛔                         | | ||||
| | plutosdr_source      | Working    | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | rfspace_source       | Working    | -                 | OPT_BUILD_RFSPACE_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| | rtl_sdr_source       | Working    | librtlsdr         | OPT_BUILD_RTL_SDR_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| @@ -350,7 +351,7 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
|  | ||||
| | Name               | Stage      | Dependencies | Option                       | Built by default| Built in Release | Enabled in SDR++ by default | | ||||
| |--------------------|------------|--------------|------------------------------|:---------------:|:----------------:|:---------------------------:| | ||||
| | android_audio_sink | Working    | -            | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| | android_audio_sink | Working    | -            | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔              | ✅              | ✅ (Android only)          | | ||||
| | audio_sink         | Working    | rtaudio      | OPT_BUILD_AUDIO_SINK         | ✅              | ✅              | ✅                         | | ||||
| | network_sink       | Working    | -            | OPT_BUILD_NETWORK_SINK       | ✅              | ✅              | ✅                         | | ||||
| | new_portaudio_sink | Beta       | portaudio    | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| @@ -376,9 +377,9 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | discord_integration | Working    | -            | OPT_BUILD_DISCORD_PRESENCE  | ✅              | ✅               | ⛔                         | | ||||
| | frequency_manager   | Working    | -            | OPT_BUILD_FREQUENCY_MANAGER | ✅              | ✅               | ✅                         | | ||||
| | recorder            | Working    | -            | OPT_BUILD_RECORDER          | ✅              | ✅               | ✅                         | | ||||
| | rigctl_client       | Unfinished | -            | OPT_BUILD_RIGCTL_CLIENT     | ⛔              | ⛔               | ⛔                         | | ||||
| | rigctl_client       | Unfinished | -            | OPT_BUILD_RIGCTL_CLIENT     | ✅              | ✅               | ⛔                         | | ||||
| | rigctl_server       | Working    | -            | OPT_BUILD_RIGCTL_SERVER     | ✅              | ✅               | ✅                         | | ||||
| | scanner             | Beta       | -            | OPT_BUILD_SCANNER           | ✅              | ✅               | ✅                         | | ||||
| | scanner             | Beta       | -            | OPT_BUILD_SCANNER           | ✅              | ✅               | ⛔                         | | ||||
| | scheduler           | Unfinished | -            | OPT_BUILD_SCHEDULER         | ⛔              | ⛔               | ⛔                         | | ||||
|  | ||||
| # Troubleshooting | ||||
|   | ||||
							
								
								
									
										29
									
								
								source_modules/perseus_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								source_modules/perseus_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(perseus_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_link_directories(perseus_source PRIVATE "C:/Users/ryzerth/Documents/Code/libperseus-sdr/build/Debug") | ||||
|  | ||||
|     target_include_directories(perseus_source PUBLIC "C:/Users/ryzerth/Documents/Code/libperseus-sdr/src") | ||||
|  | ||||
|     target_link_libraries(perseus_source PRIVATE perseus-sdr) | ||||
| else (MSVC) | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBPERSEUSSDR REQUIRED libperseus-sdr) | ||||
|  | ||||
|     target_include_directories(perseus_source PRIVATE ${LIBPERSEUSSDR_INCLUDE_DIRS}) | ||||
|     target_link_directories(perseus_source PRIVATE ${LIBPERSEUSSDR_LIBRARY_DIRS}) | ||||
|     target_link_libraries(perseus_source PRIVATE ${LIBPERSEUSSDR_LIBRARIES}) | ||||
|  | ||||
|     # Include it because for some reason pkgconfig doesn't look here? | ||||
|     if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") | ||||
|         target_include_directories(perseus_source PRIVATE "/usr/local/include") | ||||
|     endif() | ||||
|      | ||||
| endif () | ||||
							
								
								
									
										468
									
								
								source_modules/perseus_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										468
									
								
								source_modules/perseus_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,468 @@ | ||||
| #include <utils/flog.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <config.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <gui/widgets/stepped_slider.h> | ||||
| #include <perseus-sdr.h> | ||||
| #include <utils/optionlist.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "perseus_source", | ||||
|     /* Description:     */ "Perseus SDR source module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| #define MAX_SAMPLERATE_COUNT    128 | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| class PerseusSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     PerseusSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 768000; | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected; | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &stream; | ||||
|  | ||||
|         perseus_set_debug(9); | ||||
|  | ||||
|         refresh(); | ||||
|  | ||||
|         config.acquire(); | ||||
|         std::string serial = config.conf["device"]; | ||||
|         config.release(); | ||||
|         select(serial); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("Perseus", &handler); | ||||
|     } | ||||
|  | ||||
|     ~PerseusSourceModule() { | ||||
|         stop(this); | ||||
|         sigpath::sourceManager.unregisterSource("Perseus"); | ||||
|         if (libInit) { perseus_exit(); } | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
|     void refresh() { | ||||
|         // Re-initialize driver | ||||
|         if (libInit) { perseus_exit(); } | ||||
|         int devCount = perseus_init(); | ||||
|         if (devCount < 0) { | ||||
|             libInit = false; | ||||
|             flog::error("Could not initialize libperseus: {}", perseus_errorstr()); | ||||
|             return; | ||||
|         } | ||||
|         libInit = true; | ||||
|  | ||||
|         // Open each device to get the serial number | ||||
|         for (int i = 0; i < devCount; i++) { | ||||
|             // Open device | ||||
|             perseus_descr* dev = perseus_open(i); | ||||
|             if (!dev) { | ||||
|                 flog::error("Failed to open Perseus device with ID {}: {}", i, perseus_errorstr()); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Load firmware | ||||
|             int err = perseus_firmware_download(dev, NULL); | ||||
|             if (err) { | ||||
|                 flog::error("Could not upload firmware to device {}: {}", i, perseus_errorstr()); | ||||
|                 perseus_close(dev); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Get info | ||||
|             eeprom_prodid prodId; | ||||
|             err = perseus_get_product_id(dev, &prodId); | ||||
|             if (err) { | ||||
|                 flog::error("Could not getproduct info from device {}: {}", i, perseus_errorstr()); | ||||
|                 perseus_close(dev); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Create entry | ||||
|             char serial[128]; | ||||
|             char buf[128]; | ||||
|             sprintf(serial, "%05d", (int)prodId.sn); | ||||
|             sprintf(buf, "Perseus %d.%d [%s]", (int)prodId.hwver, (int)prodId.hwrel, serial); | ||||
|             devList.define(serial, buf, i); | ||||
|  | ||||
|             // Close device | ||||
|             perseus_close(dev); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devList.empty()) {  | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial number is not available, select first instead | ||||
|         if (!devList.keyExists(serial)) { | ||||
|             select(devList.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Open device | ||||
|         selectedSerial = serial; | ||||
|         selectedPerseusId = devList.value(devList.keyId(serial)); | ||||
|         perseus_descr* dev = perseus_open(selectedPerseusId); | ||||
|         if (!dev) { | ||||
|             flog::error("Failed to open device {}: {}", selectedPerseusId, perseus_errorstr()); | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Load firmware | ||||
|         int err = perseus_firmware_download(dev, NULL); | ||||
|         if (err) { | ||||
|             flog::error("Could not upload firmware to device: {}", perseus_errorstr()); | ||||
|             perseus_close(dev); | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get info | ||||
|         eeprom_prodid prodId; | ||||
|         err = perseus_get_product_id(dev, &prodId); | ||||
|         if (err) { | ||||
|             flog::error("Could not getproduct info from device: {}", perseus_errorstr()); | ||||
|             perseus_close(dev); | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // List samplerates | ||||
|         srList.clear(); | ||||
|         int samplerates[MAX_SAMPLERATE_COUNT]; | ||||
|         memset(samplerates, 0, sizeof(int)*MAX_SAMPLERATE_COUNT); | ||||
|         err = perseus_get_sampling_rates(dev, samplerates, MAX_SAMPLERATE_COUNT); | ||||
|         if (err) { | ||||
|             flog::error("Could not get samplerate list: {}", perseus_errorstr()); | ||||
|             perseus_close(dev); | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|         for (int i = 0; i < MAX_SAMPLERATE_COUNT; i++) { | ||||
|             if (!samplerates[i]) { break; } | ||||
|             srList.define(samplerates[i], getBandwdithScaled(samplerates[i]), samplerates[i]); | ||||
|         } | ||||
|  | ||||
|         // TODO: List attenuator values | ||||
|  | ||||
|         // Load options | ||||
|         srId = 0; | ||||
|         dithering = false; | ||||
|         preamp = false; | ||||
|         preselector = true; | ||||
|         atten = 0; | ||||
|         config.acquire(); | ||||
|         if (config.conf["devices"][selectedSerial].contains("samplerate")) { | ||||
|             int sr = config.conf["devices"][selectedSerial]["samplerate"]; | ||||
|             if (srList.keyExists(sr)) { | ||||
|                 srId = srList.keyId(sr); | ||||
|             } | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("dithering")) { | ||||
|             dithering = config.conf["devices"][selectedSerial]["dithering"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("preamp")) { | ||||
|             preamp = config.conf["devices"][selectedSerial]["preamp"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("preselector")) { | ||||
|             preselector = config.conf["devices"][selectedSerial]["preselector"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("attenuation")) { | ||||
|             atten = config.conf["devices"][selectedSerial]["attenuation"]; | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         // Update samplerate | ||||
|         sampleRate = srList[srId]; | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         // Close device | ||||
|         perseus_close(dev); | ||||
|     } | ||||
|  | ||||
| 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) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("PerseusSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         flog::info("PerseusSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|         if (_this->selectedSerial.empty()) { | ||||
|             flog::error("No device is selected"); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         // Open device | ||||
|         _this->openDev = perseus_open(_this->selectedPerseusId); | ||||
|         if (!_this->openDev) { | ||||
|             flog::error("Failed to open device {}: {}", _this->selectedPerseusId, perseus_errorstr()); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Load firmware | ||||
|         int err = perseus_firmware_download(_this->openDev, NULL); | ||||
|         if (err) { | ||||
|             flog::error("Could not upload firmware to device: {}", perseus_errorstr()); | ||||
|             perseus_close(_this->openDev); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Set samplerate | ||||
|         err = perseus_set_sampling_rate(_this->openDev, _this->sampleRate); | ||||
|         if (err) { | ||||
|             flog::error("Could not set samplerate: {}", perseus_errorstr()); | ||||
|             perseus_close(_this->openDev); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Set options | ||||
|         perseus_set_adc(_this->openDev, _this->dithering, _this->preamp); | ||||
|         perseus_set_attenuator_in_db(_this->openDev, _this->atten); | ||||
|         perseus_set_ddc_center_freq(_this->openDev, _this->freq, _this->preselector); | ||||
|  | ||||
|         // Start stream | ||||
|         int idealBufferSize = _this->sampleRate / 200; | ||||
|         int multipleOf1024 = std::clamp<int>(idealBufferSize / 1024, 1, 2); | ||||
|         int bufferSize = multipleOf1024 * 1024; | ||||
|         int bufferBytes = bufferSize*6; | ||||
|         err = perseus_start_async_input(_this->openDev, bufferBytes, callback, _this); | ||||
|         if (err) { | ||||
|             flog::error("Could not start stream: {}", perseus_errorstr()); | ||||
|             perseus_close(_this->openDev); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         _this->running = true; | ||||
|         flog::info("PerseusSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|  | ||||
|         // Stop stream | ||||
|         _this->stream.stopWriter(); | ||||
|         perseus_stop_async_input(_this->openDev); | ||||
|         _this->stream.clearWriteStop(); | ||||
|  | ||||
|         // Close device | ||||
|         perseus_close(_this->openDev); | ||||
|  | ||||
|         flog::info("PerseusSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             perseus_set_ddc_center_freq(_this->openDev, freq, _this->preselector); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("PerseusSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|  | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devList.txt)) { | ||||
|             std::string serial = _this->devList.key(_this->devId); | ||||
|             _this->select(serial); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = serial; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_airspyhf_sr_sel_", _this->name), &_this->srId, _this->srList.txt)) { | ||||
|             _this->sampleRate = _this->srList[_this->srId]; | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["samplerate"] = _this->sampleRate; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::LeftLabel("Attenuation"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderFloatWithSteps(CONCAT("##_airspyhf_atten_", _this->name), &_this->atten, 0, 30, 10, SmGui::FMT_STR_FLOAT_DB_NO_DECIMAL)) { | ||||
|             if (_this->running) { | ||||
|                 perseus_set_attenuator_in_db(_this->openDev, _this->atten); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["attenuation"] = _this->atten; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Checkbox(CONCAT("Preamp##_airspyhf_preamp_", _this->name), &_this->preamp)) { | ||||
|             if (_this->running) { | ||||
|                 perseus_set_adc(_this->openDev, _this->dithering, _this->preamp); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["preamp"] = _this->preamp; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Checkbox(CONCAT("Dithering##_airspyhf_dither_", _this->name), &_this->dithering)) { | ||||
|             if (_this->running) { | ||||
|                 perseus_set_adc(_this->openDev, _this->dithering, _this->preamp); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["dithering"] = _this->dithering; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Checkbox(CONCAT("Preselector##_airspyhf_presel_", _this->name), &_this->preselector)) { | ||||
|             if (_this->running) { | ||||
|                 perseus_set_ddc_center_freq(_this->openDev, _this->freq, _this->preselector); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["preselector"] = _this->preselector; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static int callback(void* buf, int bufferSize, void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         uint8_t* samples = (uint8_t*)buf; | ||||
|         int sampleCount = bufferSize / 6; | ||||
|         for (int i = 0; i < sampleCount; i++) { | ||||
|             int32_t re, im; | ||||
|             re = *(samples++); | ||||
|             re |= *(samples++) << 8; | ||||
|             re |= *(samples++) << 16; | ||||
|             re |= (re >> 23) * (0xFF << 24); // Sign extend | ||||
|             im = *(samples++); | ||||
|             im |= *(samples++) << 8; | ||||
|             im |= *(samples++) << 16; | ||||
|             im |= (im >> 23) * (0xFF << 24); // Sign extend | ||||
|             _this->stream.writeBuf[i].re = ((float)re / (float)0x7FFFFF); | ||||
|             _this->stream.writeBuf[i].im = ((float)im / (float)0x7FFFFF); | ||||
|         } | ||||
|         _this->stream.swap(sampleCount); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     int sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     bool libInit = false; | ||||
|     perseus_descr* openDev; | ||||
|     std::string selectedSerial = ""; | ||||
|     int selectedPerseusId; | ||||
|     float atten = 0; | ||||
|     bool preamp = false; | ||||
|     bool dithering = false; | ||||
|     bool preselector = true; | ||||
|  | ||||
|     OptionList<std::string, int> devList; | ||||
|     OptionList<int, int> srList; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     json def = json({}); | ||||
|     def["devices"] = json({}); | ||||
|     def["device"] = ""; | ||||
|     config.setPath(core::args["root"].s() + "/perseus_config.json"); | ||||
|     config.load(def); | ||||
|     config.enableAutoSave(); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new PerseusSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { | ||||
|     delete (PerseusSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| @@ -28,7 +28,7 @@ public: | ||||
|         this->name = name; | ||||
|  | ||||
|         strcpy(hostname, "localhost"); | ||||
|         sampleRate = 41000000.0; | ||||
|         sampleRate = 5750000.0; | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
| @@ -103,8 +103,14 @@ private: | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         SpectranHTTPSourceModule* _this = (SpectranHTTPSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             // TODO | ||||
|         bool connected = (_this->client && _this->client->isOpen()); | ||||
|         if (connected) { | ||||
|             int64_t newfreq = round(freq); | ||||
|             if (newfreq != _this->lastReportedFreq && _this->gotReport) { | ||||
|                 flog::debug("Sending tuning command"); | ||||
|                 _this->lastReportedFreq = newfreq; | ||||
|                 _this->client->setCenterFrequency(newfreq); | ||||
|             } | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("SpectranHTTPSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
| @@ -138,6 +144,8 @@ private: | ||||
|             _this->tryConnect(); | ||||
|         } | ||||
|         else if (connected && SmGui::Button("Disconnect##spectran_http_source")) { | ||||
|             _this->client->onCenterFrequencyChanged.unbind(_this->onFreqChangedId); | ||||
|             _this->client->onCenterFrequencyChanged.unbind(_this->onSamplerateChangedId); | ||||
|             _this->client->close(); | ||||
|         } | ||||
|         if (_this->running) { style::endDisabled(); } | ||||
| @@ -154,13 +162,28 @@ private: | ||||
|  | ||||
|     void tryConnect() { | ||||
|         try { | ||||
|             gotReport = false; | ||||
|             client = std::make_shared<SpectranHTTPClient>(hostname, port, &stream); | ||||
|             onFreqChangedId = client->onCenterFrequencyChanged.bind(&SpectranHTTPSourceModule::onFreqChanged, this); | ||||
|             onSamplerateChangedId = client->onSamplerateChanged.bind(&SpectranHTTPSourceModule::onSamplerateChanged, this); | ||||
|             client->startWorker(); | ||||
|         } | ||||
|         catch (std::runtime_error e) { | ||||
|             flog::error("Could not connect: {0}", e.what()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void onFreqChanged(double newFreq) { | ||||
|         if (lastReportedFreq == newFreq) { return; } | ||||
|         lastReportedFreq = newFreq; | ||||
|         tuner::tune(tuner::TUNER_MODE_IQ_ONLY, "", newFreq); | ||||
|         gotReport = true; | ||||
|     } | ||||
|  | ||||
|     void onSamplerateChanged(double newSr) { | ||||
|         core::setInputSampleRate(newSr); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     double sampleRate; | ||||
| @@ -168,11 +191,16 @@ private: | ||||
|     bool running = false; | ||||
|  | ||||
|     std::shared_ptr<SpectranHTTPClient> client; | ||||
|     HandlerID onFreqChangedId; | ||||
|     HandlerID onSamplerateChangedId; | ||||
|  | ||||
|     double freq; | ||||
|  | ||||
|     int64_t lastReportedFreq = 0; | ||||
|     bool gotReport; | ||||
|  | ||||
|     char hostname[1024]; | ||||
|     int port = 80; | ||||
|     int port = 54664; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|  | ||||
| }; | ||||
|   | ||||
| @@ -5,6 +5,8 @@ SpectranHTTPClient::SpectranHTTPClient(std::string host, int port, dsp::stream<d | ||||
|     this->stream = stream; | ||||
|  | ||||
|     // Connect to server | ||||
|     this->host = host; | ||||
|     this->port = port; | ||||
|     sock = net::connect(host, port); | ||||
|     http = net::http::Client(sock); | ||||
|  | ||||
| @@ -14,6 +16,13 @@ SpectranHTTPClient::SpectranHTTPClient(std::string host, int port, dsp::stream<d | ||||
|     net::http::ResponseHeader rshdr; | ||||
|     http.recvResponseHeader(rshdr, 5000); | ||||
|  | ||||
|     if (rshdr.getStatusCode() != net::http::STATUS_CODE_OK) { | ||||
|         flog::error("HTTP request did not return ok: {}", rshdr.getStatusString()); | ||||
|         throw std::runtime_error("HTTP request did not return ok"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void SpectranHTTPClient::startWorker() { | ||||
|     // Start chunk worker | ||||
|     workerThread = std::thread(&SpectranHTTPClient::worker, this); | ||||
| } | ||||
| @@ -33,6 +42,27 @@ void SpectranHTTPClient::close() { | ||||
|     stream->clearWriteStop(); | ||||
| } | ||||
|  | ||||
| void SpectranHTTPClient::setCenterFrequency(uint64_t freq) { | ||||
|     // Connect to control endpoint (TODO: Switch to an always connected endpoint) | ||||
|     auto controlSock = net::connect(host, port); | ||||
|     auto controlHttp = net::http::Client(controlSock); | ||||
|  | ||||
|     // Make request | ||||
|     net::http::RequestHeader rqhdr(net::http::METHOD_PUT, "/control", host); | ||||
|     char buf[1024]; | ||||
|     sprintf(buf, "{\"frequencyCenter\":%d,\"frequencySpan\":%d,\"type\":\"capture\"}", freq, _samplerate); | ||||
|     std::string data = buf; | ||||
|     char lenBuf[16]; | ||||
|     sprintf(lenBuf, "%d", data.size()); | ||||
|     rqhdr.setField("Content-Length", lenBuf); | ||||
|     controlHttp.sendRequestHeader(rqhdr); | ||||
|     controlSock->sendstr(data); | ||||
|     net::http::ResponseHeader rshdr; | ||||
|     controlHttp.recvResponseHeader(rshdr, 5000); | ||||
|  | ||||
|     flog::debug("Response: {}", rshdr.getStatusString()); | ||||
| } | ||||
|  | ||||
| void SpectranHTTPClient::worker() { | ||||
|     while (sock->isOpen()) { | ||||
|         // Get chunk header | ||||
| @@ -52,6 +82,40 @@ void SpectranHTTPClient::worker() { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Decode JSON (yes, this is hacky, but it must be extremely fast) | ||||
|         auto startFreqBegin = jsonData.find("\"startFrequency\":"); | ||||
|         auto startFreqEnd = jsonData.find(',', startFreqBegin); | ||||
|         std::string startFreqStr = jsonData.substr(startFreqBegin + 17, startFreqEnd - startFreqBegin - 17); | ||||
|         int64_t startFreq = std::stoll(startFreqStr); | ||||
|  | ||||
|         auto endFreqBegin = jsonData.find("\"endFrequency\":"); | ||||
|         auto endFreqEnd = jsonData.find(',', endFreqBegin); | ||||
|         std::string endFreqStr = jsonData.substr(endFreqBegin + 15, endFreqEnd - endFreqBegin - 15); | ||||
|         int64_t endFreq = std::stoll(endFreqStr); | ||||
|  | ||||
|         auto sampleFreqBegin = jsonData.find("\"sampleFrequency\":"); | ||||
|         bool sampleFreqReceived = (sampleFreqBegin != -1); | ||||
|         int64_t sampleFreq; | ||||
|         if (sampleFreqReceived) { | ||||
|             auto sampleFreqEnd = jsonData.find(',', sampleFreqBegin); | ||||
|             std::string sampleFreqStr = jsonData.substr(sampleFreqBegin + 18, sampleFreqEnd - sampleFreqBegin - 18); | ||||
|             sampleFreq = std::stoll(sampleFreqStr); | ||||
|         } | ||||
|          | ||||
|         // Calculate and update center freq | ||||
|         int64_t samplerate = sampleFreqReceived ? sampleFreq : (endFreq - startFreq); | ||||
|         int64_t centerFreq = round(((double)endFreq + (double)startFreq) / 2.0); | ||||
|         if (centerFreq != _centerFreq) { | ||||
|             flog::debug("New center freq: {}", centerFreq); | ||||
|             _centerFreq = centerFreq; | ||||
|             onCenterFrequencyChanged(centerFreq); | ||||
|         } | ||||
|         if (samplerate != _samplerate) { | ||||
|             flog::debug("New samplerate: {}", samplerate); | ||||
|             _samplerate = samplerate; | ||||
|             onSamplerateChanged(samplerate); | ||||
|         } | ||||
|  | ||||
|         // Read (and check for) record separator | ||||
|         uint8_t rs; | ||||
|         int rslen = sock->recv(&rs, 1, true, 5000); | ||||
| @@ -72,10 +136,11 @@ void SpectranHTTPClient::worker() { | ||||
|             i += read; | ||||
|             sampLen += read; | ||||
|         } | ||||
|         int sampCount = sampLen / 8; | ||||
|  | ||||
|         // Swap to stream | ||||
|         if (streamingEnabled) { | ||||
|             if (!stream->swap(sampLen / 8)) { return; } | ||||
|             if (!stream->swap(sampCount)) { return; } | ||||
|         } | ||||
|          | ||||
|         // Read trailing CRLF | ||||
|   | ||||
| @@ -4,22 +4,35 @@ | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <utils/proto/http.h> | ||||
| #include <utils/new_event.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
| class SpectranHTTPClient { | ||||
| public: | ||||
|     SpectranHTTPClient(std::string host, int port, dsp::stream<dsp::complex_t>* stream); | ||||
|  | ||||
|     void startWorker(); | ||||
|     void streaming(bool enabled); | ||||
|     bool isOpen(); | ||||
|     void close(); | ||||
|  | ||||
|     void setCenterFrequency(uint64_t freq); | ||||
|  | ||||
|     NewEvent<uint64_t> onCenterFrequencyChanged; | ||||
|     NewEvent<uint64_t> onSamplerateChanged; | ||||
|  | ||||
| private: | ||||
|     void worker(); | ||||
|  | ||||
|     std::string host; | ||||
|     int port; | ||||
|  | ||||
|     std::shared_ptr<net::Socket> sock; | ||||
|     net::http::Client http; | ||||
|     dsp::stream<dsp::complex_t>* stream; | ||||
|     std::thread workerThread; | ||||
|  | ||||
|     bool streamingEnabled = false; | ||||
|     int64_t _centerFreq = 0; | ||||
|     uint64_t _samplerate = 0; | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user