kopia lustrzana https://github.com/f4exb/sdrangel
				
				
				
			
						commit
						66efc10006
					
				|  | @ -20,3 +20,5 @@ debian/files | |||
| debian/sdrangel.debhelper.log | ||||
| debian/debhelper-build-stamp | ||||
| obj-x86_64-linux-gnu/* | ||||
| **/venv/ | ||||
| *.pyc | ||||
|  |  | |||
|  | @ -31,6 +31,8 @@ option(SANITIZE_ADDRESS "Activate memory address sanitization" OFF) | |||
| option(HOST_RPI "Compiling on RPi" OFF) | ||||
| option(RX_SAMPLE_24BIT "Internal 24 bit Rx DSP" OFF) | ||||
| option(NO_DSP_SIMD "Do not use SIMD instructions for DSP even if available" OFF) | ||||
| option(BUILD_SERVER "Build Server" ON) | ||||
| option(BUILD_GUI "Build GUI" ON) | ||||
| 
 | ||||
| list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) | ||||
| 
 | ||||
|  | @ -211,7 +213,11 @@ else() | |||
|     message(STATUS "Compiling with SIMD instructions for DSP if available") | ||||
| endif() | ||||
| 
 | ||||
| set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -Woverloaded-virtual -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") | ||||
| if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -Woverloaded-virtual -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") | ||||
| elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W3 -MP ${EXTRA_FLAGS}") | ||||
| endif() | ||||
| 
 | ||||
| if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") | ||||
|     if (BUILD_DEBIAN) | ||||
|  | @ -224,8 +230,12 @@ endif() | |||
| ############################################################################## | ||||
| # base libraries | ||||
| add_subdirectory(sdrbase) | ||||
| if (BUILD_GUI) | ||||
| add_subdirectory(sdrgui) | ||||
| endif() | ||||
| if (BUILD_SERVER) | ||||
| add_subdirectory(sdrsrv) | ||||
| endif() | ||||
| add_subdirectory(sdrbench) | ||||
| add_subdirectory(httpserver) | ||||
| add_subdirectory(logging) | ||||
|  | @ -250,6 +260,7 @@ qt5_add_external_resources(sdrbase.rcc sdrbase/resources/res.qrc) | |||
| 
 | ||||
| ############################################################################## | ||||
| # main GUI application | ||||
| if (BUILD_GUI) | ||||
| 
 | ||||
| set(sdrangel_SOURCES | ||||
|     app/main.cpp | ||||
|  | @ -281,9 +292,11 @@ if(WIN32) | |||
| endif(WIN32) | ||||
| 
 | ||||
| target_link_libraries(sdrangel Qt5::Widgets Qt5::Multimedia) | ||||
| endif() | ||||
| 
 | ||||
| ############################################################################## | ||||
| # main server application | ||||
| if (BUILD_SERVER) | ||||
| 
 | ||||
| set(sdrangelsrv_SOURCES | ||||
|     appsrv/main.cpp | ||||
|  | @ -305,6 +318,7 @@ target_link_libraries(sdrangelsrv | |||
| ) | ||||
| 
 | ||||
| target_link_libraries(sdrangelsrv Qt5::Multimedia) | ||||
| endif() | ||||
| 
 | ||||
| ############################################################################## | ||||
| # main benchmark application | ||||
|  | @ -350,8 +364,12 @@ if (BUILD_DEBIAN) | |||
| endif (BUILD_DEBIAN) | ||||
| 
 | ||||
| add_subdirectory(devices) | ||||
| if (BUILD_GUI) | ||||
| add_subdirectory(plugins) | ||||
| endif() | ||||
| if (BUILD_SERVER) | ||||
| add_subdirectory(pluginssrv) | ||||
| endif() | ||||
| 
 | ||||
| if(LIBUSB_FOUND AND UNIX) | ||||
|     add_subdirectory(fcdhid) | ||||
|  | @ -361,15 +379,21 @@ endif(LIBUSB_FOUND AND UNIX) | |||
| ############################################################################## | ||||
| 
 | ||||
| #install targets | ||||
| if (BUILD_GUI) | ||||
| install(TARGETS sdrangel DESTINATION bin) | ||||
| endif() | ||||
| if (BUILD_SERVER) | ||||
| install(TARGETS sdrangelsrv DESTINATION bin) | ||||
| endif() | ||||
| install(TARGETS sdrangelbench DESTINATION bin) | ||||
| #install(TARGETS sdrbase DESTINATION lib) | ||||
| 
 | ||||
| #install files and directories | ||||
| install(DIRECTORY udev-rules DESTINATION share/sdrangel) | ||||
| install(FILES udev-rules/install.sh DESTINATION share/sdrangel/udev-rules PERMISSIONS WORLD_EXECUTE) | ||||
| install(FILES udev-rules/install.sh DESTINATION share/sdrangel/udev-rules PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) | ||||
| install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdrbase.rcc DESTINATION bin) | ||||
| install(FILES desktop/sdrangel.desktop DESTINATION share/applications) | ||||
| install(FILES desktop/sdrangel_icon.png DESTINATION share/pixmaps) | ||||
| 
 | ||||
| ############################################################################## | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo | |||
| */ | ||||
| 	QCoreApplication::setOrganizationName("f4exb"); | ||||
| 	QCoreApplication::setApplicationName("SDRangel"); | ||||
|     QCoreApplication::setApplicationVersion("4.3.1"); | ||||
|     QCoreApplication::setApplicationVersion("4.3.2"); | ||||
| #if QT_VERSION >= 0x050600 | ||||
| 	QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // DPI support
 | ||||
|     QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); //HiDPI pixmaps
 | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo | |||
| 
 | ||||
|     QCoreApplication::setOrganizationName("f4exb"); | ||||
|     QCoreApplication::setApplicationName("SDRangelBench"); | ||||
|     QCoreApplication::setApplicationVersion("4.3.1"); | ||||
|     QCoreApplication::setApplicationVersion("4.3.2"); | ||||
| 
 | ||||
|     int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; | ||||
|     std::vector<int> vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo | |||
| 
 | ||||
|     QCoreApplication::setOrganizationName("f4exb"); | ||||
|     QCoreApplication::setApplicationName("SDRangelSrv"); | ||||
|     QCoreApplication::setApplicationVersion("4.3.1"); | ||||
|     QCoreApplication::setApplicationVersion("4.3.2"); | ||||
| 
 | ||||
|     int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; | ||||
|     std::vector<int> vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); | ||||
|  |  | |||
|  | @ -1,3 +1,12 @@ | |||
| sdrangel (4.3.2-1) unstable; urgency=medium | ||||
| 
 | ||||
|   * Reverse API to forward device and channel changes to external application | ||||
|   * Funcube dongle: fixed segfault when stopping device | ||||
|   * Channel analzyer: fixed rational downsampler range | ||||
|   * SoapySDR support: fixed memory leaks | ||||
| 
 | ||||
|  -- Edouard Griffiths, F4EXB <f4exb06@gmail.com>  Sun, 09 Dec 2018 21:14:18 +0100 | ||||
| 
 | ||||
| sdrangel (4.3.1-1) unstable; urgency=medium | ||||
| 
 | ||||
|   * RTL-SDR: offset tuning support | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ Name=SDRangel | |||
| GenericName=SDR/Analyzer frontend | ||||
| Comment=SDR/Analyzer frontend for Airspy, BladeRF, HackRF, RTL-SDR and FunCube | ||||
| Exec=sdrangel | ||||
| Icon=sdrangel_icon.xpm | ||||
| Icon=sdrangel_icon.png | ||||
| StartupNotify=true | ||||
| Terminal=false | ||||
| Type=Application | ||||
|  |  | |||
										
											Plik binarny nie jest wyświetlany.
										
									
								
							| Po Szerokość: | Wysokość: | Rozmiar: 4.9 KiB | 
|  | @ -28,19 +28,19 @@ else(BUILD_DEBIAN) | |||
|     if(LIBUSB_FOUND AND LIMESUITE_FOUND) | ||||
|         add_subdirectory(limesdr) | ||||
|     endif(LIBUSB_FOUND AND LIMESUITE_FOUND) | ||||
|      | ||||
| 
 | ||||
|     find_package(LibIIO) | ||||
|     if(LIBUSB_FOUND AND LIBIIO_FOUND) | ||||
|         add_subdirectory(plutosdr) | ||||
|     endif(LIBUSB_FOUND AND LIBIIO_FOUND) | ||||
|      | ||||
| 
 | ||||
|     find_package(LibPerseus) | ||||
|     if(LIBUSB_FOUND AND LIBPERSEUS_FOUND) | ||||
|         add_subdirectory(perseus) | ||||
|     endif() | ||||
|      | ||||
| 
 | ||||
|     find_package(SoapySDR) | ||||
|     if(LIBUSB_FOUND AND SOAPYSDR_FOUND) | ||||
|     if(SOAPYSDR_FOUND) | ||||
|         add_subdirectory(soapysdr) | ||||
|     endif() | ||||
| endif (BUILD_DEBIAN) | ||||
|  |  | |||
|  | @ -35,6 +35,8 @@ add_library(bladerf1device SHARED | |||
|     ${bladerf1device_SOURCES} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(bladerf1device PROPERTIES DEFINE_SYMBOL "devices_EXPORTS") | ||||
| 
 | ||||
| if (BUILD_DEBIAN) | ||||
| target_link_libraries(bladerf1device | ||||
|     bladerf | ||||
|  |  | |||
|  | @ -32,6 +32,8 @@ add_library(bladerf2device SHARED | |||
|     ${bladerf2device_SOURCES} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(bladerf2device PROPERTIES DEFINE_SYMBOL "devices_EXPORTS") | ||||
| 
 | ||||
| if (BUILD_DEBIAN) | ||||
| target_link_libraries(bladerf2device | ||||
|     bladerf | ||||
|  |  | |||
|  | @ -34,6 +34,8 @@ add_library(hackrfdevice SHARED | |||
|     ${hackrfdevice_SOURCES} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(hackrfdevice PROPERTIES DEFINE_SYMBOL "devices_EXPORTS") | ||||
| 
 | ||||
| if (BUILD_DEBIAN) | ||||
| target_link_libraries(hackrfdevice | ||||
|     hackrf | ||||
|  |  | |||
|  | @ -41,6 +41,8 @@ add_library(limesdrdevice SHARED | |||
|     ${limesdrdevice_SOURCES} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(limesdrdevice PROPERTIES DEFINE_SYMBOL "devices_EXPORTS") | ||||
| 
 | ||||
| if (BUILD_DEBIAN) | ||||
| target_link_libraries(limesdrdevice | ||||
|     limesuite | ||||
|  |  | |||
|  | @ -33,6 +33,8 @@ add_library(perseusdevice SHARED | |||
|     ${perseusdevice_SOURCES} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(perseusdevice PROPERTIES DEFINE_SYMBOL "devices_EXPORTS") | ||||
| 
 | ||||
| if (BUILD_DEBIAN) | ||||
| target_link_libraries(perseusdevice | ||||
|     perseus | ||||
|  |  | |||
|  | @ -39,6 +39,8 @@ add_library(plutosdrdevice SHARED | |||
|     ${plutosdrdevice_SOURCES} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(plutosdrdevice PROPERTIES DEFINE_SYMBOL "devices_EXPORTS") | ||||
| 
 | ||||
| if (BUILD_DEBIAN) | ||||
| target_link_libraries(plutosdrdevice | ||||
|     iio | ||||
|  |  | |||
|  | @ -38,6 +38,8 @@ add_library(soapysdrdevice SHARED | |||
|     ${soapysdrdevice_SOURCES} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(soapysdrdevice PROPERTIES DEFINE_SYMBOL "devices_EXPORTS") | ||||
| 
 | ||||
| if (BUILD_DEBIAN) | ||||
| target_link_libraries(soapysdrdevice | ||||
|     SoapySDR | ||||
|  |  | |||
										
											Plik binarny nie jest wyświetlany.
										
									
								
							| Przed Szerokość: | Wysokość: | Rozmiar: 12 KiB Po Szerokość: | Wysokość: | Rozmiar: 21 KiB | 
										
											Plik binarny nie jest wyświetlany.
										
									
								
							
										
											Plik binarny nie jest wyświetlany.
										
									
								
							| Po Szerokość: | Wysokość: | Rozmiar: 13 KiB | 
										
											Plik binarny nie jest wyświetlany.
										
									
								
							|  | @ -34,8 +34,8 @@ const char *fcd_traits<ProPlus>::displayedName = "FunCube Dongle Pro+"; | |||
| const char *fcd_traits<Pro>::pluginDisplayedName = "FunCube Pro Input"; | ||||
| const char *fcd_traits<ProPlus>::pluginDisplayedName = "FunCube Pro+ Input"; | ||||
| 
 | ||||
| const char *fcd_traits<Pro>::pluginVersion = "4.3.0"; | ||||
| const char *fcd_traits<ProPlus>::pluginVersion = "4.3.0"; | ||||
| const char *fcd_traits<Pro>::pluginVersion = "4.3.2"; | ||||
| const char *fcd_traits<ProPlus>::pluginVersion = "4.3.2"; | ||||
| 
 | ||||
| const int64_t fcd_traits<Pro>::loLowLimitFreq = 64000000L; | ||||
| const int64_t fcd_traits<ProPlus>::loLowLimitFreq = 150000L; | ||||
|  |  | |||
|  | @ -48,6 +48,8 @@ add_library(httpserver SHARED | |||
| 	${httpserver_HEADERS_MOC} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(httpserver PROPERTIES DEFINE_SYMBOL "httpserver_EXPORTS") | ||||
| 
 | ||||
| target_link_libraries(httpserver | ||||
| 	${QT_LIBRARIES} | ||||
| ) | ||||
|  |  | |||
|  | @ -34,6 +34,8 @@ add_library(logging SHARED | |||
| 	${logging_HEADERS_MOC} | ||||
| ) | ||||
| 
 | ||||
| set_target_properties(logging PROPERTIES DEFINE_SYMBOL "logging_EXPORTS") | ||||
| 
 | ||||
| target_link_libraries(logging | ||||
| 	${QT_LIBRARIES} | ||||
| ) | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ set(chanalyzer_FORMS | |||
| include_directories( | ||||
| 	. | ||||
| 	${CMAKE_CURRENT_BINARY_DIR} | ||||
| 	${Boost_INCLUDE_DIRS} | ||||
| ) | ||||
| 
 | ||||
| #include(${QT_USE_FILE}) | ||||
|  |  | |||
|  | @ -25,7 +25,6 @@ | |||
| #include "chanalyzer.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzer, Message) | ||||
| MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzerOld, Message) | ||||
| MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelizer, Message) | ||||
| MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgReportChannelSampleRateChanged, Message) | ||||
| 
 | ||||
|  | @ -75,20 +74,6 @@ ChannelAnalyzer::~ChannelAnalyzer() | |||
|     delete RRCFilter; | ||||
| } | ||||
| 
 | ||||
| void ChannelAnalyzer::configure(MessageQueue* messageQueue, | ||||
| 		int channelSampleRate, | ||||
| 		Real Bandwidth, | ||||
| 		Real LowCutoff, | ||||
| 		int  spanLog2, | ||||
| 		bool ssb, | ||||
| 		bool pll, | ||||
| 		bool fll, | ||||
| 		unsigned int pllPskOrder) | ||||
| { | ||||
|     Message* cmd = MsgConfigureChannelAnalyzerOld::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, fll, pllPskOrder); | ||||
| 	messageQueue->push(cmd); | ||||
| } | ||||
| 
 | ||||
| void ChannelAnalyzer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) | ||||
| { | ||||
|     (void) positiveOnly; | ||||
|  | @ -234,14 +219,16 @@ bool ChannelAnalyzer::handleMessage(const Message& cmd) | |||
|     } | ||||
| 	else | ||||
| 	{ | ||||
| 		if (m_sampleSink != 0) | ||||
| 		{ | ||||
| 		   return m_sampleSink->handleMessage(cmd); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| 	    // Processed through GUI
 | ||||
| //		if (m_sampleSink != 0)
 | ||||
| //		{
 | ||||
| //		   return m_sampleSink->handleMessage(cmd);
 | ||||
| //		}
 | ||||
| //		else
 | ||||
| //		{
 | ||||
| //			return false;
 | ||||
| //		}
 | ||||
| 	    return false; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -65,71 +65,6 @@ public: | |||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureChannelAnalyzerOld : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         int  getChannelSampleRate() const { return m_channelSampleRate; } | ||||
|         Real getBandwidth() const { return m_Bandwidth; } | ||||
|         Real getLoCutoff() const { return m_LowCutoff; } | ||||
|         int  getSpanLog2() const { return m_spanLog2; } | ||||
|         bool getSSB() const { return m_ssb; } | ||||
|         bool getPLL() const { return m_pll; } | ||||
|         bool getFLL() const { return m_fll; } | ||||
|         unsigned int getPLLPSKOrder() const { return m_pllPskOrder; } | ||||
| 
 | ||||
|         static MsgConfigureChannelAnalyzerOld* create( | ||||
|                 int channelSampleRate, | ||||
|                 Real Bandwidth, | ||||
|                 Real LowCutoff, | ||||
|                 int spanLog2, | ||||
|                 bool ssb, | ||||
|                 bool pll, | ||||
|                 bool fll, | ||||
| 				unsigned int pllPskOrder) | ||||
|         { | ||||
|             return new MsgConfigureChannelAnalyzerOld( | ||||
|                     channelSampleRate, | ||||
|                     Bandwidth, | ||||
|                     LowCutoff, | ||||
|                     spanLog2, | ||||
|                     ssb, | ||||
|                     pll, | ||||
|                     fll, | ||||
| 					pllPskOrder); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         int  m_channelSampleRate; | ||||
|         Real m_Bandwidth; | ||||
|         Real m_LowCutoff; | ||||
|         int  m_spanLog2; | ||||
|         bool m_ssb; | ||||
|         bool m_pll; | ||||
|         bool m_fll; | ||||
|         unsigned int m_pllPskOrder; | ||||
| 
 | ||||
|         MsgConfigureChannelAnalyzerOld( | ||||
|                 int channelSampleRate, | ||||
|                 Real Bandwidth, | ||||
|                 Real LowCutoff, | ||||
|                 int spanLog2, | ||||
|                 bool ssb, | ||||
|                 bool pll, | ||||
|                 bool fll, | ||||
| 				unsigned int pllPskOrder) : | ||||
|             Message(), | ||||
|             m_channelSampleRate(channelSampleRate), | ||||
|             m_Bandwidth(Bandwidth), | ||||
|             m_LowCutoff(LowCutoff), | ||||
|             m_spanLog2(spanLog2), | ||||
|             m_ssb(ssb), | ||||
|             m_pll(pll), | ||||
|             m_fll(fll), | ||||
| 			m_pllPskOrder(pllPskOrder) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureChannelizer : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|  | @ -175,15 +110,15 @@ public: | |||
| 	virtual void destroy() { delete this; } | ||||
| 	void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } | ||||
| 
 | ||||
| 	void configure(MessageQueue* messageQueue, | ||||
| 			int channelSampleRate, | ||||
| 			Real Bandwidth, | ||||
| 			Real LowCutoff, | ||||
| 			int spanLog2, | ||||
| 			bool ssb, | ||||
| 			bool pll, | ||||
| 			bool fll, | ||||
| 			unsigned int pllPskOrder); | ||||
| //	void configure(MessageQueue* messageQueue,
 | ||||
| //			int channelSampleRate,
 | ||||
| //			Real Bandwidth,
 | ||||
| //			Real LowCutoff,
 | ||||
| //			int spanLog2,
 | ||||
| //			bool ssb,
 | ||||
| //			bool pll,
 | ||||
| //			bool fll,
 | ||||
| //			unsigned int pllPskOrder);
 | ||||
| 
 | ||||
| 	DownChannelizer *getChannelizer() { return m_channelizer; } | ||||
| 	int getInputSampleRate() const { return m_inputSampleRate; } | ||||
|  |  | |||
|  | @ -24,7 +24,6 @@ INCLUDEPATH += ../../../sdrgui | |||
| 
 | ||||
| CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 | ||||
| CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(MSVC):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_69_0" | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,25 +14,25 @@ | |||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <device/devicesourceapi.h> | ||||
| #include "device/deviceuiset.h" | ||||
| #include <dsp/downchannelizer.h> | ||||
| #include <QDockWidget> | ||||
| #include <QMainWindow> | ||||
| 
 | ||||
| #include "device/devicesourceapi.h" | ||||
| #include "device/deviceuiset.h" | ||||
| #include "dsp/downchannelizer.h" | ||||
| #include "dsp/threadedbasebandsamplesink.h" | ||||
| #include "ui_chanalyzergui.h" | ||||
| #include "dsp/spectrumscopecombovis.h" | ||||
| #include "dsp/spectrumvis.h" | ||||
| #include "dsp/dspengine.h" | ||||
| #include "gui/glspectrum.h" | ||||
| #include "gui/glscope.h" | ||||
| #include "gui/basicchannelsettingsdialog.h" | ||||
| #include "plugin/pluginapi.h" | ||||
| #include "util/simpleserializer.h" | ||||
| #include "util/db.h" | ||||
| #include "dsp/dspengine.h" | ||||
| #include "mainwindow.h" | ||||
| 
 | ||||
| #include "ui_chanalyzergui.h" | ||||
| #include "chanalyzer.h" | ||||
| #include "chanalyzergui.h" | ||||
| 
 | ||||
|  | @ -101,10 +101,12 @@ void ChannelAnalyzerGUI::displaySettings() | |||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|     setWindowTitle(m_channelMarker.getTitle()); | ||||
| 
 | ||||
|     ui->channelSampleRate->setValueRange(7, 0.501*m_channelAnalyzer->getInputSampleRate(), m_channelAnalyzer->getInputSampleRate()); | ||||
|     ui->channelSampleRate->setValue(m_settings.m_downSampleRate); | ||||
| 
 | ||||
|     blockApplySettings(true); | ||||
| 
 | ||||
|     ui->useRationalDownsampler->setChecked(m_settings.m_downSample); | ||||
|     ui->channelSampleRate->setValue(m_settings.m_downSampleRate); | ||||
|     setNewFinalRate(); | ||||
|     if (m_settings.m_ssb) { | ||||
|         ui->BWLabel->setText("LP"); | ||||
|  | @ -186,9 +188,10 @@ bool ChannelAnalyzerGUI::handleMessage(const Message& message) | |||
| { | ||||
|     if (ChannelAnalyzer::MsgReportChannelSampleRateChanged::match(message)) | ||||
|     { | ||||
|         qDebug() << "ChannelAnalyzerGUI::handleMessage: MsgReportChannelSampleRateChanged"; | ||||
|         ui->channelSampleRate->setValueRange(7, 2000U, m_channelAnalyzer->getInputSampleRate()); | ||||
|         qDebug() << "ChannelAnalyzerGUI::handleMessage: MsgReportChannelSampleRateChanged:" << m_channelAnalyzer->getInputSampleRate(); | ||||
|         ui->channelSampleRate->setValueRange(7, 0.501*m_channelAnalyzer->getInputSampleRate(), m_channelAnalyzer->getInputSampleRate()); | ||||
|         ui->channelSampleRate->setValue(m_settings.m_downSampleRate); | ||||
|         m_settings.m_downSampleRate = ui->channelSampleRate->getValueNew(); | ||||
|         setNewFinalRate(); | ||||
| 
 | ||||
|         return true; | ||||
|  | @ -401,7 +404,7 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device | |||
|     ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); | ||||
| 
 | ||||
| 	ui->channelSampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); | ||||
| 	ui->channelSampleRate->setValueRange(7, 2000U, 9999999U); | ||||
| 	ui->channelSampleRate->setValueRange(7, 0.501*m_rate, m_rate); | ||||
| 
 | ||||
| 	ui->glSpectrum->setCenterFrequency(m_rate/2); | ||||
| 	ui->glSpectrum->setSampleRate(m_rate); | ||||
|  | @ -544,10 +547,6 @@ void ChannelAnalyzerGUI::applySettings(bool force) | |||
|                 ChannelAnalyzer::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); | ||||
|         m_channelAnalyzer->getInputMessageQueue()->push(msgChannelizer); | ||||
| 
 | ||||
|         ChannelAnalyzer::MsgConfigureChannelizer *msg = | ||||
|                 ChannelAnalyzer::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); | ||||
|         m_channelAnalyzer->getInputMessageQueue()->push(msg); | ||||
| 
 | ||||
|         ChannelAnalyzer::MsgConfigureChannelAnalyzer* message = | ||||
|                 ChannelAnalyzer::MsgConfigureChannelAnalyzer::create( m_settings, force); | ||||
|         m_channelAnalyzer->getInputMessageQueue()->push(message); | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ | |||
| 
 | ||||
| const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { | ||||
| 	QString("Channel Analyzer"), | ||||
| 	QString("4.3.0"), | ||||
| 	QString("4.3.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  |  | |||
|  | @ -25,6 +25,10 @@ | |||
| #include <boost/crc.hpp> | ||||
| #include <boost/cstdint.hpp> | ||||
| 
 | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| 
 | ||||
| #include "util/simpleserializer.h" | ||||
|  | @ -63,14 +67,21 @@ DaemonSink::DaemonSink(DeviceSourceAPI *deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| DaemonSink::~DaemonSink() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
|     m_dataBlockMutex.lock(); | ||||
| 
 | ||||
|     if (m_dataBlock && !m_dataBlock->m_txControlBlock.m_complete) { | ||||
|         delete m_dataBlock; | ||||
|     } | ||||
| 
 | ||||
|     m_dataBlockMutex.unlock(); | ||||
|     m_deviceAPI->removeChannelAPI(this); | ||||
|     m_deviceAPI->removeThreadedSink(m_threadedChannelizer); | ||||
|  | @ -325,23 +336,43 @@ void DaemonSink::applySettings(const DaemonSinkSettings& settings, bool force) | |||
|             << " m_dataPort: " << settings.m_dataPort | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     if ((m_settings.m_nbFECBlocks != settings.m_nbFECBlocks) || force) { | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if ((m_settings.m_nbFECBlocks != settings.m_nbFECBlocks) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("nbFECBlocks"); | ||||
|         setNbBlocksFEC(settings.m_nbFECBlocks); | ||||
|         setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_txDelay != settings.m_txDelay) || force) { | ||||
|     if ((m_settings.m_txDelay != settings.m_txDelay) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("txDelay"); | ||||
|         setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { | ||||
|     if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("dataAddress"); | ||||
|         m_dataAddress = settings.m_dataAddress; | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_dataPort != settings.m_dataPort) || force) { | ||||
|     if ((m_settings.m_dataPort != settings.m_dataPort) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("dataPort"); | ||||
|         m_dataPort = settings.m_dataPort; | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_useReverseAPI) && (reverseAPIKeys.size() != 0)) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -408,7 +439,21 @@ int DaemonSink::webapiSettingsPutPatch( | |||
|     if (channelSettingsKeys.contains("title")) { | ||||
|         settings.m_title = *response.getDaemonSinkSettings()->getTitle(); | ||||
|     } | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("useReverseAPI")) { | ||||
|         settings.m_useReverseAPI = response.getDaemonSinkSettings()->getUseReverseApi() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIAddress")) { | ||||
|         settings.m_reverseAPIAddress = *response.getDaemonSinkSettings()->getReverseApiAddress() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIPort")) { | ||||
|         settings.m_reverseAPIPort = response.getDaemonSinkSettings()->getReverseApiPort(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { | ||||
|         settings.m_reverseAPIDeviceIndex = response.getDaemonSinkSettings()->getReverseApiDeviceIndex(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { | ||||
|         settings.m_reverseAPIChannelIndex = response.getDaemonSinkSettings()->getReverseApiChannelIndex(); | ||||
|     } | ||||
| 
 | ||||
|     MsgConfigureDaemonSink *msg = MsgConfigureDaemonSink::create(settings, force); | ||||
|     m_inputMessageQueue.push(msg); | ||||
|  | @ -445,4 +490,82 @@ void DaemonSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& re | |||
|         response.getDaemonSinkSettings()->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
| 
 | ||||
|     response.getDaemonSinkSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); | ||||
| 
 | ||||
|     if (response.getDaemonSinkSettings()->getReverseApiAddress()) { | ||||
|         *response.getDaemonSinkSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; | ||||
|     } else { | ||||
|         response.getDaemonSinkSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); | ||||
|     } | ||||
| 
 | ||||
|     response.getDaemonSinkSettings()->setReverseApiPort(settings.m_reverseAPIPort); | ||||
|     response.getDaemonSinkSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); | ||||
|     response.getDaemonSinkSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); | ||||
| } | ||||
| 
 | ||||
| void DaemonSink::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DaemonSinkSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(0); | ||||
|     swgChannelSettings->setChannelType(new QString("DaemonSink")); | ||||
|     swgChannelSettings->setDaemonSinkSettings(new SWGSDRangel::SWGDaemonSinkSettings()); | ||||
|     SWGSDRangel::SWGDaemonSinkSettings *swgDaemonSinkSettings = swgChannelSettings->getDaemonSinkSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("nbFECBlocks") || force) { | ||||
|         swgDaemonSinkSettings->setNbFecBlocks(settings.m_nbFECBlocks); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("txDelay") || force) | ||||
|     { | ||||
|         swgDaemonSinkSettings->setTxDelay(settings.m_txDelay); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("dataAddress") || force) { | ||||
|         swgDaemonSinkSettings->setDataAddress(new QString(settings.m_dataAddress)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("dataPort") || force) { | ||||
|         swgDaemonSinkSettings->setDataPort(settings.m_dataPort); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgDaemonSinkSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgDaemonSinkSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void DaemonSink::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "DaemonSink::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("DaemonSink::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -25,12 +25,15 @@ | |||
| 
 | ||||
| #include <QObject> | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelsinkapi.h" | ||||
| #include "channel/sdrdaemondatablock.h" | ||||
| #include "daemonsinksettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class DeviceSourceAPI; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class DownChannelizer; | ||||
|  | @ -148,9 +151,15 @@ private: | |||
|     int m_txDelay; | ||||
|     QString m_dataAddress; | ||||
|     uint16_t m_dataPort; | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     void applySettings(const DaemonSinkSettings& settings, bool force = false); | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSinkSettings& settings); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DaemonSinkSettings& settings, bool force); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| }; | ||||
| 
 | ||||
| #endif /* INCLUDE_DAEMONSINK_H_ */ | ||||
|  |  | |||
|  | @ -113,6 +113,7 @@ DaemonSinkGUI::DaemonSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas | |||
|     ui->setupUi(this); | ||||
|     setAttribute(Qt::WA_DeleteOnClose, true); | ||||
|     connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); | ||||
|     connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); | ||||
| 
 | ||||
|     m_daemonSink = (DaemonSink*) channelrx; | ||||
|     m_daemonSink->setMessageQueueToGUI(getInputMessageQueue()); | ||||
|  | @ -217,11 +218,22 @@ void DaemonSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void DaemonSinkGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  |  | |||
|  | @ -17,6 +17,8 @@ | |||
| #ifndef PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKGUI_H_ | ||||
| #define PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKGUI_H_ | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QTime> | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ | |||
| 
 | ||||
| const PluginDescriptor DaemonSinkPlugin::m_pluginDescriptor = { | ||||
|     QString("Daemon channel Sink"), | ||||
|     QString("4.1.0"), | ||||
|     QString("4.3.2"), | ||||
|     QString("(c) Edouard Griffiths, F4EXB"), | ||||
|     QString("https://github.com/f4exb/sdrangel"), | ||||
|     true, | ||||
|  |  | |||
|  | @ -40,6 +40,11 @@ void DaemonSinkSettings::resetToDefaults() | |||
|     m_rgbColor = QColor(140, 4, 4).rgb(); | ||||
|     m_title = "Daemon sink"; | ||||
|     m_channelMarker = nullptr; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray DaemonSinkSettings::serialize() const | ||||
|  | @ -51,6 +56,11 @@ QByteArray DaemonSinkSettings::serialize() const | |||
|     s.writeU32(4, m_dataPort); | ||||
|     s.writeU32(5, m_rgbColor); | ||||
|     s.writeString(6, m_title); | ||||
|     s.writeBool(7, m_useReverseAPI); | ||||
|     s.writeString(8, m_reverseAPIAddress); | ||||
|     s.writeU32(9, m_reverseAPIPort); | ||||
|     s.writeU32(10, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(11, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -90,6 +100,20 @@ bool DaemonSinkSettings::deserialize(const QByteArray& data) | |||
| 
 | ||||
|         d.readU32(5, &m_rgbColor, QColor(0, 255, 255).rgb()); | ||||
|         d.readString(6, &m_title, "Daemon sink"); | ||||
|         d.readBool(7, &m_useReverseAPI, false); | ||||
|         d.readString(8, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(9, &tmp, 0); | ||||
| 
 | ||||
|         if ((tmp > 1023) && (tmp < 65535)) { | ||||
|             m_reverseAPIPort = tmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(10, &tmp, 0); | ||||
|         m_reverseAPIDeviceIndex = tmp > 99 ? 99 : tmp; | ||||
|         d.readU32(11, &tmp, 0); | ||||
|         m_reverseAPIChannelIndex = tmp > 99 ? 99 : tmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|  |  | |||
|  | @ -35,6 +35,11 @@ struct DaemonSinkSettings | |||
|     uint16_t m_dataPort; | ||||
|     quint32 m_rgbColor; | ||||
|     QString m_title; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2015 Edouard Griffiths, F4EXB.                                  //
 | ||||
| // Copyright (C) 2015-2018 Edouard Griffiths, F4EXB.                             //
 | ||||
| //                                                                               //
 | ||||
| // This program is free software; you can redistribute it and/or modify          //
 | ||||
| // it under the terms of the GNU General Public License as published by          //
 | ||||
|  | @ -18,6 +18,9 @@ | |||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
|  | @ -85,10 +88,15 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : | |||
|     m_pllFilt.create(101, m_audioSampleRate, 200.0); | ||||
|     m_pll.computeCoefficients(0.05, 0.707, 1000); | ||||
|     m_syncAMBuffIndex = 0; | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| AMDemod::~AMDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 	DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); | ||||
|     m_deviceAPI->removeChannelAPI(this); | ||||
|     m_deviceAPI->removeThreadedSink(m_threadedChannelizer); | ||||
|  | @ -427,8 +435,15 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | |||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_pll: " << settings.m_pll | ||||
|             << " m_syncAMOperation: " << (int) settings.m_syncAMOperation | ||||
|             << " m_useReverseAPI: " << settings.m_useReverseAPI | ||||
|             << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress | ||||
|             << " m_reverseAPIAddress: " << settings.m_reverseAPIPort | ||||
|             << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex | ||||
|             << " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || | ||||
|         (m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) | ||||
|     { | ||||
|  | @ -439,11 +454,19 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | |||
|         m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); | ||||
|         DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate); | ||||
|         m_settingsMutex.unlock(); | ||||
| 
 | ||||
|         if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { | ||||
|             reverseAPIKeys.append("rfBandwidth"); | ||||
|         } | ||||
|         if ((m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) { | ||||
|             reverseAPIKeys.append("bandpassEnable"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_squelch != settings.m_squelch) || force) | ||||
|     { | ||||
|         m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch); | ||||
|         reverseAPIKeys.append("squelch"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|  | @ -457,6 +480,8 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | |||
|         if (m_audioSampleRate != audioSampleRate) { | ||||
|             applyAudioSampleRate(audioSampleRate); | ||||
|         } | ||||
| 
 | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_pll != settings.m_pll) || force) | ||||
|  | @ -470,10 +495,38 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) | |||
|         { | ||||
|             m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003); | ||||
|         } | ||||
| 
 | ||||
|         reverseAPIKeys.append("pll"); | ||||
|         reverseAPIKeys.append("syncAMOperation"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) { | ||||
|     if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) | ||||
|     { | ||||
|         m_syncAMBuffIndex = 0; | ||||
|         reverseAPIKeys.append("pll"); | ||||
|         reverseAPIKeys.append("syncAMOperation"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_audioMute != settings.m_audioMute) || force) { | ||||
|         reverseAPIKeys.append("audioMute"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_volume != settings.m_volume) || force) { | ||||
|         reverseAPIKeys.append("volume"); | ||||
|     } | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
|  | @ -560,7 +613,23 @@ int AMDemod::webapiSettingsPutPatch( | |||
|         qint32 syncAMOperationCode = response.getAmDemodSettings()->getSyncAmOperation(); | ||||
|         settings.m_syncAMOperation = syncAMOperationCode < 0 ? | ||||
|                 AMDemodSettings::SyncAMDSB : syncAMOperationCode > 2 ? | ||||
|                         AMDemodSettings::SyncAMDSB : (AMDemodSettings::SyncAMOperation) syncAMOperationCode; | ||||
|                         AMDemodSettings::SyncAMLSB : (AMDemodSettings::SyncAMOperation) syncAMOperationCode; | ||||
|     } | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("useReverseAPI")) { | ||||
|         settings.m_useReverseAPI = response.getAmDemodSettings()->getUseReverseApi() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIAddress")) { | ||||
|         settings.m_reverseAPIAddress = *response.getAmDemodSettings()->getReverseApiAddress() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIPort")) { | ||||
|         settings.m_reverseAPIPort = response.getAmDemodSettings()->getReverseApiPort(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { | ||||
|         settings.m_reverseAPIDeviceIndex = response.getAmDemodSettings()->getReverseApiDeviceIndex(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { | ||||
|         settings.m_reverseAPIChannelIndex = response.getAmDemodSettings()->getReverseApiChannelIndex(); | ||||
|     } | ||||
| 
 | ||||
|     if (frequencyOffsetChanged) | ||||
|  | @ -620,6 +689,17 @@ void AMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respo | |||
| 
 | ||||
|     response.getAmDemodSettings()->setPll(settings.m_pll ? 1 : 0); | ||||
|     response.getAmDemodSettings()->setSyncAmOperation((int) m_settings.m_syncAMOperation); | ||||
|     response.getAmDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); | ||||
| 
 | ||||
|     if (response.getAmDemodSettings()->getReverseApiAddress()) { | ||||
|         *response.getAmDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; | ||||
|     } else { | ||||
|         response.getAmDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); | ||||
|     } | ||||
| 
 | ||||
|     response.getAmDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort); | ||||
|     response.getAmDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); | ||||
|     response.getAmDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); | ||||
| } | ||||
| 
 | ||||
| void AMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | ||||
|  | @ -634,3 +714,83 @@ void AMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | |||
|     response.getAmDemodReport()->setChannelSampleRate(m_inputSampleRate); | ||||
| } | ||||
| 
 | ||||
| void AMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMDemodSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(0); | ||||
|     swgChannelSettings->setChannelType(new QString("AMDemod")); | ||||
|     swgChannelSettings->setAmDemodSettings(new SWGSDRangel::SWGAMDemodSettings()); | ||||
|     SWGSDRangel::SWGAMDemodSettings *swgAMDemodSettings = swgChannelSettings->getAmDemodSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("audioMute") || force) { | ||||
|         swgAMDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgAMDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgAMDemodSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgAMDemodSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelch") || force) { | ||||
|         swgAMDemodSettings->setSquelch(settings.m_squelch); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgAMDemodSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("volume") || force) { | ||||
|         swgAMDemodSettings->setVolume(settings.m_volume); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("bandpassEnable") || force) { | ||||
|         swgAMDemodSettings->setBandpassEnable(settings.m_bandpassEnable ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioDeviceName") || force) { | ||||
|         swgAMDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("pll") || force) { | ||||
|         swgAMDemodSettings->setPll(settings.m_pll); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("syncAMOperation") || force) { | ||||
|         swgAMDemodSettings->setSyncAmOperation((int) settings.m_syncAMOperation); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void AMDemod::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "AMDemod::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("AMDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2015 Edouard Griffiths, F4EXB.                                  //
 | ||||
| // Copyright (C) 2015-2018 Edouard Griffiths, F4EXB.                             //
 | ||||
| //                                                                               //
 | ||||
| // This program is free software; you can redistribute it and/or modify          //
 | ||||
| // it under the terms of the GNU General Public License as published by          //
 | ||||
|  | @ -17,9 +17,11 @@ | |||
| #ifndef INCLUDE_AMDEMOD_H | ||||
| #define INCLUDE_AMDEMOD_H | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QNetworkRequest> | ||||
| #include <QMutex> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelsinkapi.h" | ||||
| #include "dsp/nco.h" | ||||
|  | @ -35,6 +37,8 @@ | |||
| 
 | ||||
| #include "amdemodsettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class DeviceSourceAPI; | ||||
| class DownChannelizer; | ||||
| class ThreadedBasebandSampleSink; | ||||
|  | @ -204,6 +208,9 @@ private: | |||
| 
 | ||||
|     static const int m_udpBlockSize; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
| 	QMutex m_settingsMutex; | ||||
| 
 | ||||
| 	void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); | ||||
|  | @ -211,8 +218,13 @@ private: | |||
|     void applyAudioSampleRate(int sampleRate); | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMDemodSettings& settings, bool force); | ||||
| 
 | ||||
|     void processOneSample(Complex &ci); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_AMDEMOD_H
 | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| 
 | ||||
| #include <QDockWidget> | ||||
| #include <QMainWindow> | ||||
| #include <QDebug> | ||||
| 
 | ||||
| #include "amdemodgui.h" | ||||
| #include "amdemodssbdialog.h" | ||||
|  | @ -94,7 +95,6 @@ bool AMDemodGUI::deserialize(const QByteArray& data) | |||
| 
 | ||||
| bool AMDemodGUI::handleMessage(const Message& message) | ||||
| { | ||||
|     (void) message; | ||||
|     if (AMDemod::MsgConfigureAMDemod::match(message)) | ||||
|     { | ||||
|         qDebug("AMDemodGUI::handleMessage: AMDemod::MsgConfigureAMDemod"); | ||||
|  | @ -206,12 +206,22 @@ void AMDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void AMDemodGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  | @ -241,10 +251,10 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS | |||
| 	connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
 | ||||
| 
 | ||||
| 	CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); | ||||
| 	connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); | ||||
| 	connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); | ||||
| 
 | ||||
| 	CRightClickEnabler *samSidebandRightClickEnabler = new CRightClickEnabler(ui->ssb); | ||||
|     connect(samSidebandRightClickEnabler, SIGNAL(rightClick()), this, SLOT(samSSBSelect())); | ||||
|     connect(samSidebandRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(samSSBSelect())); | ||||
| 
 | ||||
|     ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); | ||||
|     ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); | ||||
|  | @ -335,17 +345,32 @@ void AMDemodGUI::displaySettings() | |||
|     ui->bandpassEnable->setChecked(m_settings.m_bandpassEnable); | ||||
|     ui->pll->setChecked(m_settings.m_pll); | ||||
| 
 | ||||
|     if (m_settings.m_pll) { | ||||
|         if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMLSB) { | ||||
|     qDebug() << "AMDemodGUI::displaySettings:" | ||||
|             << " m_pll: " << m_settings.m_pll | ||||
|             << " m_syncAMOperation: " << m_settings.m_syncAMOperation; | ||||
| 
 | ||||
|     if (m_settings.m_pll) | ||||
|     { | ||||
|         if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMLSB) | ||||
|         { | ||||
|             m_samUSB = false; | ||||
|             ui->ssb->setChecked(true); | ||||
|             ui->ssb->setIcon(m_iconDSBLSB); | ||||
|         } else { | ||||
|         } | ||||
|         else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) | ||||
|         { | ||||
|             m_samUSB = true; | ||||
|             ui->ssb->setChecked(true); | ||||
|             ui->ssb->setIcon(m_iconDSBUSB); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             ui->ssb->setChecked(false); | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         ui->ssb->setChecked(false); | ||||
|         ui->ssb->setIcon(m_iconDSBUSB); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| 
 | ||||
| const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { | ||||
| 	QString("AM Demodulator"), | ||||
| 	QString("4.1.0"), | ||||
| 	QString("4.3.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  |  | |||
|  | @ -40,6 +40,11 @@ void AMDemodSettings::resetToDefaults() | |||
|     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; | ||||
|     m_pll = false; | ||||
|     m_syncAMOperation = SyncAMDSB; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray AMDemodSettings::serialize() const | ||||
|  | @ -60,6 +65,11 @@ QByteArray AMDemodSettings::serialize() const | |||
|     s.writeString(11, m_audioDeviceName); | ||||
|     s.writeBool(12, m_pll); | ||||
|     s.writeS32(13, (int) m_syncAMOperation); | ||||
|     s.writeBool(14, m_useReverseAPI); | ||||
|     s.writeString(15, m_reverseAPIAddress); | ||||
|     s.writeU32(16, m_reverseAPIPort); | ||||
|     s.writeU32(17, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(18, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -78,6 +88,7 @@ bool AMDemodSettings::deserialize(const QByteArray& data) | |||
|     { | ||||
|         QByteArray bytetmp; | ||||
|         qint32 tmp; | ||||
|         uint32_t utmp; | ||||
|         QString strtmp; | ||||
| 
 | ||||
|         d.readS32(1, &m_inputFrequencyOffset, 0); | ||||
|  | @ -100,6 +111,20 @@ bool AMDemodSettings::deserialize(const QByteArray& data) | |||
|         d.readBool(12, &m_pll, false); | ||||
|         d.readS32(13, &tmp, 0); | ||||
|         m_syncAMOperation = tmp < 0 ? SyncAMDSB : tmp > 2 ? SyncAMDSB : (SyncAMOperation) tmp; | ||||
|         d.readBool(14, &m_useReverseAPI, false); | ||||
|         d.readString(15, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(16, &utmp, 0); | ||||
| 
 | ||||
|         if ((utmp > 1023) && (utmp < 65535)) { | ||||
|             m_reverseAPIPort = utmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(17, &utmp, 0); | ||||
|         m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; | ||||
|         d.readU32(18, &utmp, 0); | ||||
|         m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|  |  | |||
|  | @ -42,6 +42,11 @@ struct AMDemodSettings | |||
|     QString m_audioDeviceName; | ||||
|     bool m_pll; | ||||
|     SyncAMOperation m_syncAMOperation; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     AMDemodSettings(); | ||||
|     void resetToDefaults(); | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ set(atv_FORMS | |||
| include_directories( | ||||
| 	. | ||||
| 	${CMAKE_CURRENT_BINARY_DIR} | ||||
| 	${Boost_INCLUDE_DIRS} | ||||
| ) | ||||
| 
 | ||||
| #include(${QT_USE_FILE}) | ||||
|  |  | |||
|  | @ -26,7 +26,6 @@ CONFIG(Release):build_subdir = release | |||
| CONFIG(Debug):build_subdir = debug | ||||
| 
 | ||||
| CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(MSVC):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_69_0" | ||||
| 
 | ||||
|  |  | |||
|  | @ -36,6 +36,7 @@ include_directories( | |||
| 	. | ||||
| 	${CMAKE_CURRENT_BINARY_DIR} | ||||
|     ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client	 | ||||
|     ${Boost_INCLUDE_DIRS} | ||||
| ) | ||||
| 
 | ||||
| #include(${QT_USE_FILE}) | ||||
|  |  | |||
|  | @ -15,12 +15,16 @@ | |||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include "boost/format.hpp" | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGBFMDemodSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
|  | @ -100,10 +104,16 @@ BFMDemod::BFMDemod(DeviceSourceAPI *deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| BFMDemod::~BFMDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 
 | ||||
| 	DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); | ||||
| 
 | ||||
| 	m_deviceAPI->removeChannelAPI(this); | ||||
|  | @ -449,15 +459,38 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) | |||
|             << " m_showPilot: " << settings.m_showPilot | ||||
|             << " m_rdsActive: " << settings.m_rdsActive | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_useReverseAPI: " << settings.m_useReverseAPI | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
|     if ((settings.m_volume != m_settings.m_volume) || force) { | ||||
|         reverseAPIKeys.append("volume"); | ||||
|     } | ||||
|     if ((settings.m_audioStereo != m_settings.m_audioStereo) || force) { | ||||
|         reverseAPIKeys.append("audioStereo"); | ||||
|     } | ||||
|     if ((settings.m_lsbStereo != m_settings.m_lsbStereo) || force) { | ||||
|         reverseAPIKeys.append("lsbStereo"); | ||||
|     } | ||||
|     if ((settings.m_showPilot != m_settings.m_showPilot) || force) { | ||||
|         reverseAPIKeys.append("showPilot"); | ||||
|     } | ||||
|     if ((settings.m_rdsActive != m_settings.m_rdsActive) || force) { | ||||
|         reverseAPIKeys.append("rdsActive"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioStereo && (settings.m_audioStereo != m_settings.m_audioStereo)) || force) | ||||
|     { | ||||
|         m_pilotPLL.configure(19000.0/m_inputSampleRate, 50.0/m_inputSampleRate, 0.01); | ||||
|     } | ||||
| 
 | ||||
|     if((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) | ||||
|     if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("afBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
| 
 | ||||
|         m_interpolator.create(16, m_inputSampleRate, settings.m_afBandwidth); | ||||
|  | @ -472,11 +505,14 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) | |||
|         m_interpolatorRDSDistanceRemain = (Real) m_inputSampleRate / 250000.0; | ||||
|         m_interpolatorRDSDistance =  (Real) m_inputSampleRate / 250000.0; | ||||
| 
 | ||||
|         m_lowpass.create(21, m_audioSampleRate, settings.m_afBandwidth); | ||||
| 
 | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
|         Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_inputSampleRate; | ||||
|         Real hiCut  = (settings.m_rfBandwidth / 2.0) / m_inputSampleRate; | ||||
|  | @ -485,22 +521,15 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) | |||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) | ||||
|     { | ||||
|         m_settingsMutex.lock(); | ||||
|         qDebug() << "BFMDemod::handleMessage: m_lowpass.create"; | ||||
|         m_lowpass.create(21, m_audioSampleRate, settings.m_afBandwidth); | ||||
|         m_settingsMutex.unlock(); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || force) | ||||
|     { | ||||
|         qDebug() << "BFMDemod::handleMessage: set m_squelchLevel"; | ||||
|         reverseAPIKeys.append("squelch"); | ||||
|         m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|  | @ -512,6 +541,16 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -596,6 +635,21 @@ int BFMDemod::webapiSettingsPutPatch( | |||
|     if (channelSettingsKeys.contains("audioDeviceName")) { | ||||
|         settings.m_audioDeviceName = *response.getBfmDemodSettings()->getAudioDeviceName(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("useReverseAPI")) { | ||||
|         settings.m_useReverseAPI = response.getBfmDemodSettings()->getUseReverseApi() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIAddress")) { | ||||
|         settings.m_reverseAPIAddress = *response.getBfmDemodSettings()->getReverseApiAddress() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIPort")) { | ||||
|         settings.m_reverseAPIPort = response.getBfmDemodSettings()->getReverseApiPort(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { | ||||
|         settings.m_reverseAPIDeviceIndex = response.getBfmDemodSettings()->getReverseApiDeviceIndex(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { | ||||
|         settings.m_reverseAPIChannelIndex = response.getBfmDemodSettings()->getReverseApiChannelIndex(); | ||||
|     } | ||||
| 
 | ||||
|     if (frequencyOffsetChanged) | ||||
|     { | ||||
|  | @ -654,6 +708,18 @@ void BFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp | |||
|     } else { | ||||
|         response.getBfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
| 
 | ||||
|     response.getBfmDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); | ||||
| 
 | ||||
|     if (response.getBfmDemodSettings()->getReverseApiAddress()) { | ||||
|         *response.getBfmDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; | ||||
|     } else { | ||||
|         response.getBfmDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); | ||||
|     } | ||||
| 
 | ||||
|     response.getBfmDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort); | ||||
|     response.getBfmDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); | ||||
|     response.getBfmDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); | ||||
| } | ||||
| 
 | ||||
| void BFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | ||||
|  | @ -707,3 +773,87 @@ void BFMDemod::webapiFormatRDSReport(SWGSDRangel::SWGRDSReport *report) | |||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void BFMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const BFMDemodSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(0); | ||||
|     swgChannelSettings->setChannelType(new QString("BFMDemod")); | ||||
|     swgChannelSettings->setBfmDemodSettings(new SWGSDRangel::SWGBFMDemodSettings()); | ||||
|     SWGSDRangel::SWGBFMDemodSettings *swgBFMDemodSettings = swgChannelSettings->getBfmDemodSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgBFMDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgBFMDemodSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("afBandwidth") || force) { | ||||
|         swgBFMDemodSettings->setAfBandwidth(settings.m_afBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("volume") || force) { | ||||
|         swgBFMDemodSettings->setVolume(settings.m_volume); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelch") || force) { | ||||
|         swgBFMDemodSettings->setSquelch(settings.m_squelch); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioStereo") || force) { | ||||
|         swgBFMDemodSettings->setAudioStereo(settings.m_audioStereo ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("lsbStereo") || force) { | ||||
|         swgBFMDemodSettings->setLsbStereo(settings.m_lsbStereo ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("showPilot") || force) { | ||||
|         swgBFMDemodSettings->setShowPilot(settings.m_showPilot ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rdsActive") || force) { | ||||
|         swgBFMDemodSettings->setRdsActive(settings.m_rdsActive ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgBFMDemodSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgBFMDemodSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioDeviceName") || force) { | ||||
|         swgBFMDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void BFMDemod::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "BFMDemod::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("BFMDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -18,9 +18,11 @@ | |||
| #ifndef INCLUDE_BFMDEMOD_H | ||||
| #define INCLUDE_BFMDEMOD_H | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelsinkapi.h" | ||||
| #include "dsp/nco.h" | ||||
|  | @ -39,6 +41,8 @@ | |||
| #include "rdsdemod.h" | ||||
| #include "bfmdemodsettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class DeviceSourceAPI; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class DownChannelizer; | ||||
|  | @ -48,6 +52,7 @@ namespace SWGSDRangel { | |||
| } | ||||
| 
 | ||||
| class BFMDemod : public BasebandSampleSink, public ChannelSinkAPI { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     class MsgConfigureBFMDemod : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
|  | @ -117,7 +122,7 @@ public: | |||
| 
 | ||||
| 	BFMDemod(DeviceSourceAPI *deviceAPI); | ||||
| 	virtual ~BFMDemod(); | ||||
| 	virtual void destroy() { delete this; } | ||||
|     virtual void destroy() { delete this; } | ||||
| 	void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } | ||||
| 
 | ||||
| 	int getSampleRate() const { return m_inputSampleRate; } | ||||
|  | @ -269,6 +274,9 @@ private: | |||
| 
 | ||||
|     static const int m_udpBlockSize; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
| 	void applyAudioSampleRate(int sampleRate); | ||||
|     void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); | ||||
| 	void applySettings(const BFMDemodSettings& settings, bool force = false); | ||||
|  | @ -276,6 +284,10 @@ private: | |||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const BFMDemodSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiFormatRDSReport(SWGSDRangel::SWGRDSReport *report); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const BFMDemodSettings& settings, bool force); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_BFMDEMOD_H
 | ||||
|  |  | |||
|  | @ -319,12 +319,23 @@ void BFMDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void BFMDemodGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  | @ -348,7 +359,7 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban | |||
|     ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue); | ||||
| 
 | ||||
|     CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioStereo); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); | ||||
| 
 | ||||
| 	setAttribute(Qt::WA_DeleteOnClose, true); | ||||
| 	connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); | ||||
|  |  | |||
|  | @ -48,6 +48,11 @@ void BFMDemodSettings::resetToDefaults() | |||
|     m_rgbColor = QColor(80, 120, 228).rgb(); | ||||
|     m_title = "Broadcast FM Demod"; | ||||
|     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray BFMDemodSettings::serialize() const | ||||
|  | @ -73,6 +78,11 @@ QByteArray BFMDemodSettings::serialize() const | |||
| 
 | ||||
|     s.writeString(12, m_title); | ||||
|     s.writeString(13, m_audioDeviceName); | ||||
|     s.writeBool(14, m_useReverseAPI); | ||||
|     s.writeString(15, m_reverseAPIAddress); | ||||
|     s.writeU32(16, m_reverseAPIPort); | ||||
|     s.writeU32(17, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(18, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -91,6 +101,7 @@ bool BFMDemodSettings::deserialize(const QByteArray& data) | |||
|     { | ||||
|         QByteArray bytetmp; | ||||
|         qint32 tmp; | ||||
|         uint32_t utmp; | ||||
|         QString strtmp; | ||||
| 
 | ||||
|         d.readS32(1, &tmp, 0); | ||||
|  | @ -122,6 +133,20 @@ bool BFMDemodSettings::deserialize(const QByteArray& data) | |||
| 
 | ||||
|         d.readString(12, &m_title, "Broadcast FM Demod"); | ||||
|         d.readString(13, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); | ||||
|         d.readBool(14, &m_useReverseAPI, false); | ||||
|         d.readString(15, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(16, &utmp, 0); | ||||
| 
 | ||||
|         if ((utmp > 1023) && (utmp < 65535)) { | ||||
|             m_reverseAPIPort = utmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(17, &utmp, 0); | ||||
|         m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; | ||||
|         d.readU32(18, &utmp, 0); | ||||
|         m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|  |  | |||
|  | @ -35,6 +35,11 @@ struct BFMDemodSettings | |||
|     quint32 m_rgbColor; | ||||
|     QString m_title; | ||||
|     QString m_audioDeviceName; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
|     Serializable *m_spectrumGUI; | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ | |||
| 
 | ||||
| const PluginDescriptor BFMPlugin::m_pluginDescriptor = { | ||||
| 	QString("Broadcast FM Demodulator"), | ||||
| 	QString("4.1.0"), | ||||
| 	QString("4.3.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  |  | |||
|  | @ -25,7 +25,6 @@ INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client | |||
| 
 | ||||
| CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 | ||||
| CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(MSVC):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_69_0" | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,17 +18,14 @@ QMAKE_CXXFLAGS += -msse4.1 | |||
| QMAKE_CXXFLAGS += -std=c++11 | ||||
| 
 | ||||
| CONFIG(MINGW32):LIBDSDCCSRC = "C:\softs\dsdcc" | ||||
| CONFIG(MINGW64):LIBDSDCCSRC = "C:\softs\dsdcc" | ||||
| CONFIG(MSVC):LIBDSDCCSRC = "C:\softs\dsdcc" | ||||
| CONFIG(macx):LIBDSDCCSRC = "../../../../deps/dsdcc" | ||||
| 
 | ||||
| CONFIG(MINGW32):LIBMBELIBSRC = "C:\softs\mbelib" | ||||
| CONFIG(MINGW64):LIBMBELIBSRC = "C:\softs\mbelib" | ||||
| CONFIG(MSVC):LIBMBELIBSRC = "C:\softs\mbelib" | ||||
| CONFIG(macx):LIBMBELIBSRC = "../../../../deps/mbelib" | ||||
| 
 | ||||
| CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(MSVC):INCLUDEPATH += "C:\softs\boost_1_66_0" | ||||
| CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_69_0" | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,12 +16,16 @@ | |||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <string.h> | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGDSDDemodSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
|  | @ -92,10 +96,15 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| DSDDemod::~DSDDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
|     delete[] m_sampleBuffer; | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo1); | ||||
|     DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo2); | ||||
|  | @ -479,10 +488,41 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) | |||
|             << " m_pllLock: " << settings.m_pllLock | ||||
|             << " m_highPassFilter: "<< settings.m_highPassFilter | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_traceLengthMutliplier: " << settings.m_traceLengthMutliplier | ||||
|             << " m_traceStroke: " << settings.m_traceStroke | ||||
|             << " m_traceDecay: " << settings.m_traceDecay | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
|     if ((settings.m_demodGain != m_settings.m_demodGain) || force) { | ||||
|         reverseAPIKeys.append("demodGain"); | ||||
|     } | ||||
|     if ((settings.m_audioMute != m_settings.m_audioMute) || force) { | ||||
|         reverseAPIKeys.append("audioMute"); | ||||
|     } | ||||
|     if ((settings.m_syncOrConstellation != m_settings.m_syncOrConstellation) || force) { | ||||
|         reverseAPIKeys.append("syncOrConstellation"); | ||||
|     } | ||||
|     if ((settings.m_slot1On != m_settings.m_slot1On) || force) { | ||||
|         reverseAPIKeys.append("slot1On"); | ||||
|     } | ||||
|     if ((settings.m_slot2On != m_settings.m_slot2On) || force) { | ||||
|         reverseAPIKeys.append("slot2On"); | ||||
|     } | ||||
|     if ((settings.m_demodGain != m_settings.m_demodGain) || force) { | ||||
|         reverseAPIKeys.append("demodGain"); | ||||
|     } | ||||
|     if ((settings.m_traceLengthMutliplier != m_settings.m_traceLengthMutliplier) || force) { | ||||
|         reverseAPIKeys.append("traceLengthMutliplier"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolator.create(16, m_inputSampleRate, (settings.m_rfBandwidth) / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|  | @ -493,53 +533,63 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) | |||
| 
 | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("fmDeviation"); | ||||
|         m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation)); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("squelchGate"); | ||||
|         m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
 | ||||
|         m_squelchCount = 0; // reset squelch open counter
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("squelch"); | ||||
|         // input is a value in dB
 | ||||
|         m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_volume != m_settings.m_volume) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("volume"); | ||||
|         m_dsdDecoder.setAudioGain(settings.m_volume); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_baudRate != m_settings.m_baudRate) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("baudRate"); | ||||
|         m_dsdDecoder.setBaudRate(settings.m_baudRate); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("enableCosineFiltering"); | ||||
|         m_dsdDecoder.enableCosineFiltering(settings.m_enableCosineFiltering); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("tdmaStereo"); | ||||
|         m_dsdDecoder.setTDMAStereo(settings.m_tdmaStereo); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_pllLock != m_settings.m_pllLock) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("pllLock"); | ||||
|         m_dsdDecoder.setSymbolPLLLock(settings.m_pllLock); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("highPassFilter"); | ||||
|         m_dsdDecoder.useHPMbelib(settings.m_highPassFilter); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|  | @ -552,6 +602,16 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -855,6 +915,21 @@ int DSDDemod::webapiSettingsPutPatch( | |||
|     if (channelSettingsKeys.contains("traceDecay")) { | ||||
|         settings.m_traceDecay = response.getDsdDemodSettings()->getTraceDecay(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("useReverseAPI")) { | ||||
|         settings.m_useReverseAPI = response.getDsdDemodSettings()->getUseReverseApi() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIAddress")) { | ||||
|         settings.m_reverseAPIAddress = *response.getDsdDemodSettings()->getReverseApiAddress() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIPort")) { | ||||
|         settings.m_reverseAPIPort = response.getDsdDemodSettings()->getReverseApiPort(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { | ||||
|         settings.m_reverseAPIDeviceIndex = response.getDsdDemodSettings()->getReverseApiDeviceIndex(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { | ||||
|         settings.m_reverseAPIChannelIndex = response.getDsdDemodSettings()->getReverseApiChannelIndex(); | ||||
|     } | ||||
| 
 | ||||
|     if (frequencyOffsetChanged) | ||||
|     { | ||||
|  | @ -924,6 +999,18 @@ void DSDDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp | |||
|     response.getDsdDemodSettings()->setTraceLengthMutliplier(settings.m_traceLengthMutliplier); | ||||
|     response.getDsdDemodSettings()->setTraceStroke(settings.m_traceStroke); | ||||
|     response.getDsdDemodSettings()->setTraceDecay(settings.m_traceDecay); | ||||
|     response.getDsdDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); | ||||
| 
 | ||||
|     if (response.getDsdDemodSettings()->getReverseApiAddress()) { | ||||
|         *response.getDsdDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; | ||||
|     } else { | ||||
|         response.getDsdDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); | ||||
|     } | ||||
| 
 | ||||
|     response.getDsdDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort); | ||||
|     response.getDsdDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); | ||||
|     response.getDsdDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | ||||
|  | @ -946,3 +1033,117 @@ void DSDDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response | |||
|     response.getDsdDemodReport()->setSyncRate(getDecoder().getSymbolSyncQuality()); | ||||
|     response.getDsdDemodReport()->setStatusText(new QString(updateAndGetStatusText())); | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DSDDemodSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(0); | ||||
|     swgChannelSettings->setChannelType(new QString("DSDDemod")); | ||||
|     swgChannelSettings->setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings()); | ||||
|     SWGSDRangel::SWGDSDDemodSettings *swgDSDDemodSettings = swgChannelSettings->getDsdDemodSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgDSDDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgDSDDemodSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("fmDeviation") || force) { | ||||
|         swgDSDDemodSettings->setFmDeviation(settings.m_fmDeviation); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("demodGain") || force) { | ||||
|         swgDSDDemodSettings->setDemodGain(settings.m_demodGain); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("volume") || force) { | ||||
|         swgDSDDemodSettings->setVolume(settings.m_volume); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("baudRate") || force) { | ||||
|         swgDSDDemodSettings->setBaudRate(settings.m_baudRate); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelchGate") || force) { | ||||
|         swgDSDDemodSettings->setSquelchGate(settings.m_squelchGate); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelch") || force) { | ||||
|         swgDSDDemodSettings->setSquelch(settings.m_squelch); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioMute") || force) { | ||||
|         swgDSDDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("enableCosineFiltering") || force) { | ||||
|         swgDSDDemodSettings->setEnableCosineFiltering(settings.m_enableCosineFiltering ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("syncOrConstellation") || force) { | ||||
|         swgDSDDemodSettings->setSyncOrConstellation(settings.m_syncOrConstellation ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("slot1On") || force) { | ||||
|         swgDSDDemodSettings->setSlot1On(settings.m_slot1On ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("slot2On") || force) { | ||||
|         swgDSDDemodSettings->setSlot2On(settings.m_slot2On ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("tdmaStereo") || force) { | ||||
|         swgDSDDemodSettings->setTdmaStereo(settings.m_tdmaStereo ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("pllLock") || force) { | ||||
|         swgDSDDemodSettings->setPllLock(settings.m_pllLock ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgDSDDemodSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgDSDDemodSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioDeviceName") || force) { | ||||
|         swgDSDDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("highPassFilter") || force) { | ||||
|         swgDSDDemodSettings->setHighPassFilter(settings.m_highPassFilter ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("traceLengthMutliplier") || force) { | ||||
|         swgDSDDemodSettings->setTraceLengthMutliplier(settings.m_traceLengthMutliplier); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("traceStroke") || force) { | ||||
|         swgDSDDemodSettings->setTraceStroke(settings.m_traceStroke); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("traceDecay") || force) { | ||||
|         swgDSDDemodSettings->setTraceDecay(settings.m_traceDecay); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void DSDDemod::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "DSDDemod::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("DSDDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -18,9 +18,11 @@ | |||
| #ifndef INCLUDE_DSDDEMOD_H | ||||
| #define INCLUDE_DSDDEMOD_H | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelsinkapi.h" | ||||
| #include "dsp/phasediscri.h" | ||||
|  | @ -38,11 +40,14 @@ | |||
| #include "dsddemodsettings.h" | ||||
| #include "dsddecoder.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class DeviceSourceAPI; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class DownChannelizer; | ||||
| 
 | ||||
| class DSDDemod : public BasebandSampleSink, public ChannelSinkAPI { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     class MsgConfigureDSDDemod : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
|  | @ -244,6 +249,9 @@ private: | |||
|     SignalFormat m_signalFormat;   //!< Used to keep formatting during successive calls for the same standard type
 | ||||
|     PhaseDiscriminators m_phaseDiscri; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     QMutex m_settingsMutex; | ||||
| 
 | ||||
|     static const int m_udpBlockSize; | ||||
|  | @ -255,6 +263,11 @@ private: | |||
| 
 | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DSDDemodSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DSDDemodSettings& settings, bool force); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_DSDDEMOD_H
 | ||||
|  |  | |||
|  | @ -274,12 +274,23 @@ void DSDDemodGUI::onMenuDialogCalled(const QPoint &p) | |||
| { | ||||
|     //qDebug("DSDDemodGUI::onMenuDialogCalled: x: %d y: %d", p.x(), p.y());
 | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  | @ -319,7 +330,7 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban | |||
|     connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); | ||||
| 
 | ||||
|     CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); | ||||
| 
 | ||||
| 	m_scopeVisXY = new ScopeVisXY(ui->screenTV); | ||||
| 	m_scopeVisXY->setScale(2.0); | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ | |||
| 
 | ||||
| const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { | ||||
| 	QString("DSD Demodulator"), | ||||
|     QString("4.3.1"), | ||||
|     QString("4.3.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  |  | |||
|  | @ -52,6 +52,11 @@ void DSDDemodSettings::resetToDefaults() | |||
|     m_traceStroke = 100; | ||||
|     m_traceDecay = 200; | ||||
|     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray DSDDemodSettings::serialize() const | ||||
|  | @ -87,6 +92,11 @@ QByteArray DSDDemodSettings::serialize() const | |||
|     s.writeS32(21, m_traceLengthMutliplier); | ||||
|     s.writeS32(22, m_traceStroke); | ||||
|     s.writeS32(23, m_traceDecay); | ||||
|     s.writeBool(24, m_useReverseAPI); | ||||
|     s.writeString(25, m_reverseAPIAddress); | ||||
|     s.writeU32(26, m_reverseAPIPort); | ||||
|     s.writeU32(27, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(28, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -106,6 +116,7 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) | |||
|         QByteArray bytetmp; | ||||
|         QString strtmp; | ||||
|         qint32 tmp; | ||||
|         uint32_t utmp; | ||||
| 
 | ||||
|         if (m_channelMarker) { | ||||
|             d.readBlob(17, &bytetmp); | ||||
|  | @ -147,6 +158,20 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) | |||
|         m_traceStroke = tmp < 0 ? 0 : tmp > 255 ? 255 : tmp; | ||||
|         d.readS32(23, &tmp, 200); | ||||
|         m_traceDecay = tmp < 0 ? 0 : tmp > 255 ? 255 : tmp; | ||||
|         d.readBool(24, &m_useReverseAPI, false); | ||||
|         d.readString(25, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(26, &utmp, 0); | ||||
| 
 | ||||
|         if ((utmp > 1023) && (utmp < 65535)) { | ||||
|             m_reverseAPIPort = utmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(27, &utmp, 0); | ||||
|         m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; | ||||
|         d.readU32(28, &utmp, 0); | ||||
|         m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|  |  | |||
|  | @ -45,6 +45,11 @@ struct DSDDemodSettings | |||
|     int m_traceStroke; // [0..255]
 | ||||
|     int m_traceDecay; // [0..255]
 | ||||
|     QString m_audioDeviceName; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
|     Serializable *m_scopeGUI; | ||||
|  |  | |||
|  | @ -15,11 +15,15 @@ | |||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGNFMDemodSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
|  | @ -94,10 +98,15 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| NFMDemod::~NFMDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 	DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); | ||||
| 	m_deviceAPI->removeChannelAPI(this); | ||||
|     m_deviceAPI->removeThreadedSink(m_threadedChannelizer); | ||||
|  | @ -491,10 +500,33 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) | |||
|             << " m_ctcssOn: " << settings.m_ctcssOn | ||||
|             << " m_audioMute: " << settings.m_audioMute | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_useReverseAPI: " << settings.m_useReverseAPI | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
|     if ((settings.m_volume != m_settings.m_volume) || force) { | ||||
|         reverseAPIKeys.append("volume"); | ||||
|     } | ||||
|     if ((settings.m_ctcssOn != m_settings.m_ctcssOn) || force) { | ||||
|         reverseAPIKeys.append("ctcssOn"); | ||||
|     } | ||||
|     if ((settings.m_audioMute != m_settings.m_audioMute) || force) { | ||||
|         reverseAPIKeys.append("audioMute"); | ||||
|     } | ||||
|     if ((settings.m_rgbColor != m_settings.m_rgbColor) || force) { | ||||
|         reverseAPIKeys.append("rgbColor"); | ||||
|     } | ||||
|     if ((settings.m_title != m_settings.m_title) || force) { | ||||
|         reverseAPIKeys.append("title"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|  | @ -504,11 +536,13 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) | |||
| 
 | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("fmDeviation"); | ||||
|         m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast<float>(settings.m_fmDeviation)); // integrate 4x factor
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("afBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
|         m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth); | ||||
|         m_settingsMutex.unlock(); | ||||
|  | @ -516,10 +550,18 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) | |||
| 
 | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("squelchGate"); | ||||
|         m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
 | ||||
|         m_squelchCount = 0; // reset squelch open counter
 | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || force) { | ||||
|         reverseAPIKeys.append("squelch"); | ||||
|     } | ||||
|     if ((settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) { | ||||
|         reverseAPIKeys.append("deltaSquelch"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_squelch != m_settings.m_squelch) || | ||||
|         (settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) | ||||
|     { | ||||
|  | @ -540,11 +582,13 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) | |||
| 
 | ||||
|     if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("ctcssIndex"); | ||||
|         setSelectedCtcssIndex(settings.m_ctcssIndex); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
 | ||||
|  | @ -556,6 +600,16 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -722,3 +776,93 @@ void NFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response | |||
|     response.getNfmDemodReport()->setAudioSampleRate(m_audioSampleRate); | ||||
|     response.getNfmDemodReport()->setChannelSampleRate(m_inputSampleRate); | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const NFMDemodSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(0); | ||||
|     swgChannelSettings->setChannelType(new QString("NFMDemod")); | ||||
|     swgChannelSettings->setNfmDemodSettings(new SWGSDRangel::SWGNFMDemodSettings()); | ||||
|     SWGSDRangel::SWGNFMDemodSettings *swgNFMDemodSettings = swgChannelSettings->getNfmDemodSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("afBandwidth") || force) { | ||||
|         swgNFMDemodSettings->setAfBandwidth(settings.m_afBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioMute") || force) { | ||||
|         swgNFMDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("ctcssIndex") || force) { | ||||
|         swgNFMDemodSettings->setCtcssIndex(settings.m_ctcssIndex); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("ctcssOn") || force) { | ||||
|         swgNFMDemodSettings->setCtcssOn(settings.m_ctcssOn ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("deltaSquelch") || force) { | ||||
|         swgNFMDemodSettings->setDeltaSquelch(settings.m_deltaSquelch ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("fmDeviation") || force) { | ||||
|         swgNFMDemodSettings->setFmDeviation(settings.m_fmDeviation); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgNFMDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgNFMDemodSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgNFMDemodSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelch") || force) { | ||||
|         swgNFMDemodSettings->setSquelch(settings.m_squelch); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelchGate") || force) { | ||||
|         swgNFMDemodSettings->setSquelchGate(settings.m_squelchGate); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgNFMDemodSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("volume") || force) { | ||||
|         swgNFMDemodSettings->setVolume(settings.m_volume); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioDeviceName") || force) { | ||||
|         swgNFMDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void NFMDemod::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "NFMDemod::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("NFMDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -18,9 +18,11 @@ | |||
| #ifndef INCLUDE_NFMDEMOD_H | ||||
| #define INCLUDE_NFMDEMOD_H | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelsinkapi.h" | ||||
| #include "dsp/phasediscri.h" | ||||
|  | @ -38,11 +40,14 @@ | |||
| 
 | ||||
| #include "nfmdemodsettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class DeviceSourceAPI; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class DownChannelizer; | ||||
| 
 | ||||
| class NFMDemod : public BasebandSampleSink, public ChannelSinkAPI { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     class MsgConfigureNFMDemod : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
|  | @ -235,6 +240,9 @@ private: | |||
| 
 | ||||
|     PhaseDiscriminators m_phaseDiscri; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     static const int m_udpBlockSize; | ||||
| 
 | ||||
| //    void apply(bool force = false);
 | ||||
|  | @ -243,6 +251,10 @@ private: | |||
|     void applyAudioSampleRate(int sampleRate); | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMDemodSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const NFMDemodSettings& settings, bool force); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_NFMDEMOD_H
 | ||||
|  |  | |||
|  | @ -227,12 +227,22 @@ void NFMDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void NFMDemodGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  | @ -263,7 +273,7 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban | |||
| 	connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); | ||||
| 
 | ||||
|     CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); | ||||
| 
 | ||||
|     blockApplySettings(true); | ||||
| 
 | ||||
|  |  | |||
|  | @ -52,6 +52,11 @@ void NFMDemodSettings::resetToDefaults() | |||
|     m_rgbColor = QColor(255, 0, 0).rgb(); | ||||
|     m_title = "NFM Demodulator"; | ||||
|     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray NFMDemodSettings::serialize() const | ||||
|  | @ -75,6 +80,11 @@ QByteArray NFMDemodSettings::serialize() const | |||
| 
 | ||||
|     s.writeString(14, m_title); | ||||
|     s.writeString(15, m_audioDeviceName); | ||||
|     s.writeBool(16, m_useReverseAPI); | ||||
|     s.writeString(17, m_reverseAPIAddress); | ||||
|     s.writeU32(18, m_reverseAPIPort); | ||||
|     s.writeU32(19, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(20, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -93,6 +103,7 @@ bool NFMDemodSettings::deserialize(const QByteArray& data) | |||
|     { | ||||
|         QByteArray bytetmp; | ||||
|         qint32 tmp; | ||||
|         uint32_t utmp; | ||||
| 
 | ||||
|         if (m_channelMarker) | ||||
|         { | ||||
|  | @ -119,6 +130,20 @@ bool NFMDemodSettings::deserialize(const QByteArray& data) | |||
|         d.readBool(12, &m_deltaSquelch, false); | ||||
|         d.readString(14, &m_title, "NFM Demodulator"); | ||||
|         d.readString(15, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); | ||||
|         d.readBool(16, &m_useReverseAPI, false); | ||||
|         d.readString(17, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(18, &utmp, 0); | ||||
| 
 | ||||
|         if ((utmp > 1023) && (utmp < 65535)) { | ||||
|             m_reverseAPIPort = utmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(19, &utmp, 0); | ||||
|         m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; | ||||
|         d.readU32(20, &utmp, 0); | ||||
|         m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|  |  | |||
|  | @ -41,6 +41,11 @@ struct NFMDemodSettings | |||
|     quint32 m_rgbColor; | ||||
|     QString m_title; | ||||
|     QString m_audioDeviceName; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| 
 | ||||
| const PluginDescriptor NFMPlugin::m_pluginDescriptor = { | ||||
| 	QString("NFM Demodulator"), | ||||
| 	QString("4.2.1"), | ||||
| 	QString("4.3.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  |  | |||
|  | @ -17,9 +17,13 @@ | |||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <stdio.h> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGSSBDemodSettings.h" | ||||
|  | @ -98,10 +102,15 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| SSBDemod::~SSBDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 	DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); | ||||
| 
 | ||||
| 	m_deviceAPI->removeChannelAPI(this); | ||||
|  | @ -455,6 +464,18 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) | |||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if((m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
|     if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|     } | ||||
|     if((m_settings.m_lowCutoff != settings.m_lowCutoff) || force) { | ||||
|         reverseAPIKeys.append("lowCutoff"); | ||||
|     } | ||||
| 
 | ||||
|     if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || | ||||
|         (m_settings.m_lowCutoff != settings.m_lowCutoff) || force) | ||||
|     { | ||||
|  | @ -491,10 +512,24 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) | |||
| 
 | ||||
|     if ((m_settings.m_volume != settings.m_volume) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("volume"); | ||||
|         m_volume = settings.m_volume; | ||||
|         m_volume /= 4.0; // for 3276.8
 | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) || force) { | ||||
|         reverseAPIKeys.append("agcTimeLog2"); | ||||
|     } | ||||
|     if ((m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) || force) { | ||||
|         reverseAPIKeys.append("agcPowerThreshold"); | ||||
|     } | ||||
|     if ((m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) || force) { | ||||
|         reverseAPIKeys.append("agcThresholdGate"); | ||||
|     } | ||||
|     if ((m_settings.m_agcClamping != settings.m_agcClamping) || force) { | ||||
|         reverseAPIKeys.append("agcClamping"); | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) || | ||||
|         (m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) || | ||||
|         (m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) || | ||||
|  | @ -542,6 +577,7 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) | |||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); | ||||
|         audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); | ||||
|  | @ -552,6 +588,25 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_spanLog2 != settings.m_spanLog2) || force) { | ||||
|         reverseAPIKeys.append("spanLog2"); | ||||
|     } | ||||
|     if ((m_settings.m_audioBinaural != settings.m_audioBinaural) || force) { | ||||
|         reverseAPIKeys.append("audioBinaural"); | ||||
|     } | ||||
|     if ((m_settings.m_audioFlipChannels != settings.m_audioFlipChannels) || force) { | ||||
|         reverseAPIKeys.append("audioFlipChannels"); | ||||
|     } | ||||
|     if ((m_settings.m_dsb != settings.m_dsb) || force) { | ||||
|         reverseAPIKeys.append("dsb"); | ||||
|     } | ||||
|     if ((m_settings.m_audioMute != settings.m_audioMute) || force) { | ||||
|         reverseAPIKeys.append("audioMute"); | ||||
|     } | ||||
|     if ((m_settings.m_agc != settings.m_agc) || force) { | ||||
|         reverseAPIKeys.append("agc"); | ||||
|     } | ||||
| 
 | ||||
|     m_spanLog2 = settings.m_spanLog2; | ||||
|     m_audioBinaual = settings.m_audioBinaural; | ||||
|     m_audioFlipChannels = settings.m_audioFlipChannels; | ||||
|  | @ -559,6 +614,16 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) | |||
|     m_audioMute = settings.m_audioMute; | ||||
|     m_agcActive = settings.m_agc; | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -736,3 +801,101 @@ void SSBDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response | |||
|     response.getSsbDemodReport()->setChannelSampleRate(m_inputSampleRate); | ||||
| } | ||||
| 
 | ||||
| void SSBDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const SSBDemodSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(0); | ||||
|     swgChannelSettings->setChannelType(new QString("SSBDemod")); | ||||
|     swgChannelSettings->setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings()); | ||||
|     SWGSDRangel::SWGSSBDemodSettings *swgSSBDemodSettings = swgChannelSettings->getSsbDemodSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgSSBDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgSSBDemodSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("lowCutoff") || force) { | ||||
|         swgSSBDemodSettings->setLowCutoff(settings.m_lowCutoff); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("volume") || force) { | ||||
|         swgSSBDemodSettings->setVolume(settings.m_volume); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("spanLog2") || force) { | ||||
|         swgSSBDemodSettings->setSpanLog2(settings.m_spanLog2); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioBinaural") || force) { | ||||
|         swgSSBDemodSettings->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioFlipChannels") || force) { | ||||
|         swgSSBDemodSettings->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("dsb") || force) { | ||||
|         swgSSBDemodSettings->setDsb(settings.m_dsb ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioMute") || force) { | ||||
|         swgSSBDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("agc") || force) { | ||||
|         swgSSBDemodSettings->setAgc(settings.m_agc ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("agcClamping") || force) { | ||||
|         swgSSBDemodSettings->setAgcClamping(settings.m_agcClamping ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("agcTimeLog2") || force) { | ||||
|         swgSSBDemodSettings->setAgcTimeLog2(settings.m_agcTimeLog2); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("agcPowerThreshold") || force) { | ||||
|         swgSSBDemodSettings->setAgcPowerThreshold(settings.m_agcPowerThreshold); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("agcThresholdGate") || force) { | ||||
|         swgSSBDemodSettings->setAgcThresholdGate(settings.m_agcThresholdGate); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgSSBDemodSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgSSBDemodSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioDeviceName") || force) { | ||||
|         swgSSBDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void SSBDemod::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "SSBDemod::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("SSBDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -18,9 +18,11 @@ | |||
| #ifndef INCLUDE_SSBDEMOD_H | ||||
| #define INCLUDE_SSBDEMOD_H | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelsinkapi.h" | ||||
| #include "dsp/ncof.h" | ||||
|  | @ -36,11 +38,14 @@ | |||
| #define ssbFftLen 1024 | ||||
| #define agcTarget 3276.8 // -10 dB amplitude => -20 dB power: center of normal signal
 | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class DeviceSourceAPI; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class DownChannelizer; | ||||
| 
 | ||||
| class SSBDemod : public BasebandSampleSink, public ChannelSinkAPI { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
|     class MsgConfigureSSBDemod : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
|  | @ -310,6 +315,9 @@ private: | |||
| 	AudioFifo m_audioFifo; | ||||
| 	quint32 m_audioSampleRate; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
| 	QMutex m_settingsMutex; | ||||
| 
 | ||||
| 	void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); | ||||
|  | @ -317,6 +325,10 @@ private: | |||
|     void applyAudioSampleRate(int sampleRate); | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBDemodSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const SSBDemodSettings& settings, bool force); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_SSBDEMOD_H
 | ||||
|  |  | |||
|  | @ -238,12 +238,23 @@ void SSBDemodGUI::on_flipSidebands_clicked(bool checked) | |||
| void SSBDemodGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  | @ -281,7 +292,7 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban | |||
| 	m_ssbDemod->setSampleSink(m_spectrumVis); | ||||
| 
 | ||||
|     CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); | ||||
| 
 | ||||
|     ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); | ||||
|     ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); | ||||
|  |  | |||
|  | @ -55,6 +55,11 @@ void SSBDemodSettings::resetToDefaults() | |||
|     m_rgbColor = QColor(0, 255, 0).rgb(); | ||||
|     m_title = "SSB Demodulator"; | ||||
|     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray SSBDemodSettings::serialize() const | ||||
|  | @ -81,6 +86,11 @@ QByteArray SSBDemodSettings::serialize() const | |||
|     s.writeBool(15, m_agcClamping); | ||||
|     s.writeString(16, m_title); | ||||
|     s.writeString(17, m_audioDeviceName); | ||||
|     s.writeBool(18, m_useReverseAPI); | ||||
|     s.writeString(19, m_reverseAPIAddress); | ||||
|     s.writeU32(20, m_reverseAPIPort); | ||||
|     s.writeU32(21, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(22, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -99,6 +109,7 @@ bool SSBDemodSettings::deserialize(const QByteArray& data) | |||
|     { | ||||
|         QByteArray bytetmp; | ||||
|         qint32 tmp; | ||||
|         uint32_t utmp; | ||||
|         QString strtmp; | ||||
| 
 | ||||
|         d.readS32(1, &m_inputFrequencyOffset, 0); | ||||
|  | @ -126,6 +137,20 @@ bool SSBDemodSettings::deserialize(const QByteArray& data) | |||
|         d.readBool(15, &m_agcClamping, false); | ||||
|         d.readString(16, &m_title, "SSB Demodulator"); | ||||
|         d.readString(17, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); | ||||
|         d.readBool(18, &m_useReverseAPI, false); | ||||
|         d.readString(19, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(20, &utmp, 0); | ||||
| 
 | ||||
|         if ((utmp > 1023) && (utmp < 65535)) { | ||||
|             m_reverseAPIPort = utmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(21, &utmp, 0); | ||||
|         m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; | ||||
|         d.readU32(22, &utmp, 0); | ||||
|         m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|  |  | |||
|  | @ -40,6 +40,11 @@ struct SSBDemodSettings | |||
|     quint32 m_rgbColor; | ||||
|     QString m_title; | ||||
|     QString m_audioDeviceName; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
|     Serializable *m_spectrumGUI; | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
| 
 | ||||
| const PluginDescriptor SSBPlugin::m_pluginDescriptor = { | ||||
| 	QString("SSB Demodulator"), | ||||
| 	QString("4.1.0"), | ||||
| 	QString("4.3.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  |  | |||
|  | @ -16,11 +16,15 @@ | |||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGWFMDemodSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
|  | @ -74,10 +78,15 @@ WFMDemod::WFMDemod(DeviceSourceAPI* deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| WFMDemod::~WFMDemod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 	DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); | ||||
| 
 | ||||
| 	m_deviceAPI->removeChannelAPI(this); | ||||
|  | @ -317,8 +326,39 @@ void WFMDemod::applySettings(const WFMDemodSettings& settings, bool force) | |||
|             << " m_volume: " << settings.m_volume | ||||
|             << " m_squelch: " << settings.m_squelch | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_audioMute: " << settings.m_audioMute | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
|     if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|     } | ||||
|     if((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { | ||||
|         reverseAPIKeys.append("afBandwidth"); | ||||
|     } | ||||
|     if((settings.m_volume != m_settings.m_volume) || force) { | ||||
|         reverseAPIKeys.append("volume"); | ||||
|     } | ||||
|     if((settings.m_squelch != m_settings.m_squelch) || force) { | ||||
|         reverseAPIKeys.append("squelch"); | ||||
|     } | ||||
|     if((settings.m_audioMute != m_settings.m_audioMute) || force) { | ||||
|         reverseAPIKeys.append("audioMute"); | ||||
|     } | ||||
|     if((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|     } | ||||
|     if((settings.m_title != m_settings.m_title) || force) { | ||||
|         reverseAPIKeys.append("title"); | ||||
|     } | ||||
|     if((settings.m_rgbColor != m_settings.m_rgbColor) || force) { | ||||
|         reverseAPIKeys.append("rgbColor"); | ||||
|     } | ||||
| 
 | ||||
|     if((settings.m_afBandwidth != m_settings.m_afBandwidth) || | ||||
|        (settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|  | @ -356,6 +396,16 @@ void WFMDemod::applySettings(const WFMDemodSettings& settings, bool force) | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -500,3 +550,77 @@ void WFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response | |||
|     response.getWfmDemodReport()->setChannelSampleRate(m_inputSampleRate); | ||||
| } | ||||
| 
 | ||||
| void WFMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const WFMDemodSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(0); | ||||
|     swgChannelSettings->setChannelType(new QString("WFMDemod")); | ||||
|     swgChannelSettings->setWfmDemodSettings(new SWGSDRangel::SWGWFMDemodSettings()); | ||||
|     SWGSDRangel::SWGWFMDemodSettings *swgWFMDemodSettings = swgChannelSettings->getWfmDemodSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgWFMDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgWFMDemodSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("afBandwidth") || force) { | ||||
|         swgWFMDemodSettings->setAfBandwidth(settings.m_afBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("volume") || force) { | ||||
|         swgWFMDemodSettings->setVolume(settings.m_volume); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelch") || force) { | ||||
|         swgWFMDemodSettings->setSquelch(settings.m_squelch); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioMute") || force) { | ||||
|         swgWFMDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgWFMDemodSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgWFMDemodSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioDeviceName") || force) { | ||||
|         swgWFMDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void WFMDemod::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "WFMDemod::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("WFMDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -18,9 +18,11 @@ | |||
| #ifndef INCLUDE_WFMDEMOD_H | ||||
| #define INCLUDE_WFMDEMOD_H | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelsinkapi.h" | ||||
| #include "dsp/nco.h" | ||||
|  | @ -36,11 +38,14 @@ | |||
| 
 | ||||
| #define rfFilterFftLength 1024 | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class ThreadedBasebandSampleSink; | ||||
| class DownChannelizer; | ||||
| class DeviceSourceAPI; | ||||
| 
 | ||||
| class WFMDemod : public BasebandSampleSink, public ChannelSinkAPI { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     class MsgConfigureWFMDemod : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
|  | @ -203,6 +208,9 @@ private: | |||
| 
 | ||||
| 	PhaseDiscriminators m_phaseDiscri; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     static const int m_udpBlockSize; | ||||
| 
 | ||||
|     void applyAudioSampleRate(int sampleRate); | ||||
|  | @ -211,6 +219,10 @@ private: | |||
| 
 | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMDemodSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const WFMDemodSettings& settings, bool force); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| }; | ||||
| 
 | ||||
| #endif // INCLUDE_WFMDEMOD_H
 | ||||
|  |  | |||
|  | @ -169,12 +169,23 @@ void WFMDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void WFMDemodGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  | @ -202,7 +213,7 @@ WFMDemodGUI::WFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban | |||
| 	connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); | ||||
| 
 | ||||
|     CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); | ||||
| 
 | ||||
| 	ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); | ||||
|     ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); | ||||
|  |  | |||
|  | @ -45,6 +45,11 @@ void WFMDemodSettings::resetToDefaults() | |||
|     m_rgbColor = QColor(0, 0, 255).rgb(); | ||||
|     m_title = "WFM Demodulator"; | ||||
|     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray WFMDemodSettings::serialize() const | ||||
|  | @ -63,6 +68,11 @@ QByteArray WFMDemodSettings::serialize() const | |||
|         s.writeBlob(11, m_channelMarker->serialize()); | ||||
|     } | ||||
| 
 | ||||
|     s.writeBool(12, m_useReverseAPI); | ||||
|     s.writeString(13, m_reverseAPIAddress); | ||||
|     s.writeU32(14, m_reverseAPIPort); | ||||
|     s.writeU32(15, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(16, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -81,6 +91,7 @@ bool WFMDemodSettings::deserialize(const QByteArray& data) | |||
|     { | ||||
|         QByteArray bytetmp; | ||||
|         qint32 tmp; | ||||
|         uint32_t utmp; | ||||
|         QString strtmp; | ||||
| 
 | ||||
|         d.readS32(1, &tmp, 0); | ||||
|  | @ -103,6 +114,21 @@ bool WFMDemodSettings::deserialize(const QByteArray& data) | |||
|             m_channelMarker->deserialize(bytetmp); | ||||
|         } | ||||
| 
 | ||||
|         d.readBool(12, &m_useReverseAPI, false); | ||||
|         d.readString(13, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(14, &utmp, 0); | ||||
| 
 | ||||
|         if ((utmp > 1023) && (utmp < 65535)) { | ||||
|             m_reverseAPIPort = utmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(15, &utmp, 0); | ||||
|         m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; | ||||
|         d.readU32(16, &utmp, 0); | ||||
|         m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|  |  | |||
|  | @ -33,6 +33,11 @@ struct WFMDemodSettings | |||
|     quint32 m_rgbColor; | ||||
|     QString m_title; | ||||
|     QString m_audioDeviceName; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
| 
 | ||||
| const PluginDescriptor WFMPlugin::m_pluginDescriptor = { | ||||
| 	QString("WFM Demodulator"), | ||||
| 	QString("4.1.0"), | ||||
| 	QString("4.3.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  |  | |||
|  | @ -17,6 +17,9 @@ | |||
| 
 | ||||
| #include <QUdpSocket> | ||||
| #include <QHostAddress> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGUDPSinkSettings.h" | ||||
|  | @ -110,10 +113,15 @@ UDPSink::UDPSink(DeviceSourceAPI *deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSink(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| UDPSink::~UDPSink() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 	delete m_audioSocket; | ||||
| 	delete m_udpBuffer24; | ||||
|     delete m_udpBuffer16; | ||||
|  | @ -505,6 +513,57 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) | |||
|             << " m_audioPort: " << settings.m_audioPort | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
|     if ((settings.m_audioActive != m_settings.m_audioActive) || force) { | ||||
|         reverseAPIKeys.append("audioActive"); | ||||
|     } | ||||
|     if ((settings.m_audioStereo != m_settings.m_audioStereo) || force) { | ||||
|         reverseAPIKeys.append("audioStereo"); | ||||
|     } | ||||
|     if ((settings.m_gain != m_settings.m_gain) || force) { | ||||
|         reverseAPIKeys.append("gain"); | ||||
|     } | ||||
|     if ((settings.m_volume != m_settings.m_volume) || force) { | ||||
|         reverseAPIKeys.append("volume"); | ||||
|     } | ||||
|     if ((settings.m_squelchEnabled != m_settings.m_squelchEnabled) || force) { | ||||
|         reverseAPIKeys.append("squelchEnabled"); | ||||
|     } | ||||
|     if ((settings.m_squelchdB != m_settings.m_squelchdB) || force) { | ||||
|         reverseAPIKeys.append("squelchDB"); | ||||
|     } | ||||
|     if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { | ||||
|         reverseAPIKeys.append("squelchGate"); | ||||
|     } | ||||
|     if ((settings.m_agc != m_settings.m_agc) || force) { | ||||
|         reverseAPIKeys.append("agc"); | ||||
|     } | ||||
|     if ((settings.m_sampleFormat != m_settings.m_sampleFormat) || force) { | ||||
|         reverseAPIKeys.append("sampleFormat"); | ||||
|     } | ||||
|     if ((settings.m_outputSampleRate != m_settings.m_outputSampleRate) || force) { | ||||
|         reverseAPIKeys.append("outputSampleRate"); | ||||
|     } | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|     } | ||||
|     if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { | ||||
|         reverseAPIKeys.append("fmDeviation"); | ||||
|     } | ||||
|     if ((settings.m_udpAddress != m_settings.m_udpAddress) || force) { | ||||
|         reverseAPIKeys.append("udpAddress"); | ||||
|     } | ||||
|     if ((settings.m_udpPort != m_settings.m_udpPort) || force) { | ||||
|         reverseAPIKeys.append("udpPort"); | ||||
|     } | ||||
|     if ((settings.m_audioPort != m_settings.m_audioPort) || force) { | ||||
|         reverseAPIKeys.append("audioPort"); | ||||
|     } | ||||
| 
 | ||||
|     m_settingsMutex.lock(); | ||||
| 
 | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || | ||||
|  | @ -617,6 +676,16 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) | |||
| 
 | ||||
|     m_settingsMutex.unlock(); | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -798,3 +867,108 @@ void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | |||
|     response.getUdpSinkReport()->setSquelch(m_squelchOpen ? 1 : 0); | ||||
|     response.getUdpSinkReport()->setInputSampleRate(m_inputSampleRate); | ||||
| } | ||||
| 
 | ||||
| void UDPSink::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const UDPSinkSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(0); | ||||
|     swgChannelSettings->setChannelType(new QString("UDPSink")); | ||||
|     swgChannelSettings->setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); | ||||
|     SWGSDRangel::SWGUDPSinkSettings *swgUDPSinkSettings = swgChannelSettings->getUdpSinkSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("outputSampleRate") || force) { | ||||
|         swgUDPSinkSettings->setOutputSampleRate(settings.m_outputSampleRate); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("sampleFormat") || force) { | ||||
|         swgUDPSinkSettings->setSampleFormat((int) settings.m_sampleFormat); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgUDPSinkSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgUDPSinkSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("fmDeviation") || force) { | ||||
|         swgUDPSinkSettings->setFmDeviation(settings.m_fmDeviation); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("channelMute") || force) { | ||||
|         swgUDPSinkSettings->setChannelMute(settings.m_channelMute ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("gain") || force) { | ||||
|         swgUDPSinkSettings->setGain(settings.m_gain); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelchDB") || force) { | ||||
|         swgUDPSinkSettings->setSquelchDb(settings.m_squelchdB); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelchGate") || force) { | ||||
|         swgUDPSinkSettings->setSquelchGate(settings.m_squelchGate); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("squelchEnabled") || force) { | ||||
|         swgUDPSinkSettings->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("agc") || force) { | ||||
|         swgUDPSinkSettings->setAgc(settings.m_agc ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioActive") || force) { | ||||
|         swgUDPSinkSettings->setAudioActive(settings.m_audioActive ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioStereo") || force) { | ||||
|         swgUDPSinkSettings->setAudioStereo(settings.m_audioStereo ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("volume") || force) { | ||||
|         swgUDPSinkSettings->setVolume(settings.m_volume); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("udpAddress") || force) { | ||||
|         swgUDPSinkSettings->setUdpAddress(new QString(settings.m_udpAddress)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("udpPort") || force) { | ||||
|         swgUDPSinkSettings->setUdpPort(settings.m_udpPort); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioPort") || force) { | ||||
|         swgUDPSinkSettings->setAudioPort(settings.m_audioPort); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgUDPSinkSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgUDPSinkSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void UDPSink::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "UDPSink::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("UDPSink::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ | |||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QHostAddress> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesink.h" | ||||
| #include "channel/channelsinkapi.h" | ||||
|  | @ -36,6 +37,8 @@ | |||
| 
 | ||||
| #include "udpsinksettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class QUdpSocket; | ||||
| class DeviceSourceAPI; | ||||
| class ThreadedBasebandSampleSink; | ||||
|  | @ -135,6 +138,9 @@ public: | |||
| public slots: | ||||
|     void audioReadyRead(); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| 
 | ||||
| protected: | ||||
| 	class MsgUDPSinkSpectrum : public Message { | ||||
| 		MESSAGE_CLASS_DECLARATION | ||||
|  | @ -227,6 +233,9 @@ protected: | |||
|     MagAGC m_agc; | ||||
|     Bandpass<double> m_bandpass; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
| 	QMutex m_settingsMutex; | ||||
| 
 | ||||
|     void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = true); | ||||
|  | @ -234,6 +243,7 @@ protected: | |||
| 
 | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const UDPSinkSettings& settings, bool force); | ||||
| 
 | ||||
|     inline void calculateSquelch(double value) | ||||
|     { | ||||
|  |  | |||
|  | @ -615,13 +615,23 @@ void UDPSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void UDPSinkGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ | |||
| 
 | ||||
| const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { | ||||
| 	QString("UDP Channel Sink"), | ||||
| 	QString("4.1.0"), | ||||
| 	QString("4.3.2"), | ||||
| 	QString("(c) Edouard Griffiths, F4EXB"), | ||||
| 	QString("https://github.com/f4exb/sdrangel"), | ||||
| 	true, | ||||
|  |  | |||
|  | @ -49,6 +49,11 @@ void UDPSinkSettings::resetToDefaults() | |||
|     m_audioPort = 9997; | ||||
|     m_rgbColor = QColor(225, 25, 99).rgb(); | ||||
|     m_title = "UDP Sample Sink"; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray UDPSinkSettings::serialize() const | ||||
|  | @ -80,6 +85,11 @@ QByteArray UDPSinkSettings::serialize() const | |||
|     s.writeString(20, m_udpAddress); | ||||
|     s.writeU32(21, m_udpPort); | ||||
|     s.writeU32(22, m_audioPort); | ||||
|     s.writeBool(23, m_useReverseAPI); | ||||
|     s.writeString(24, m_reverseAPIAddress); | ||||
|     s.writeU32(25, m_reverseAPIPort); | ||||
|     s.writeU32(26, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(27, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| 
 | ||||
|  | @ -155,6 +165,21 @@ bool UDPSinkSettings::deserialize(const QByteArray& data) | |||
|             m_audioPort = 9997; | ||||
|         } | ||||
| 
 | ||||
|         d.readBool(23, &m_useReverseAPI, false); | ||||
|         d.readString(24, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(25, &u32tmp, 0); | ||||
| 
 | ||||
|         if ((u32tmp > 1023) && (u32tmp < 65535)) { | ||||
|             m_reverseAPIPort = u32tmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(26, &u32tmp, 0); | ||||
|         m_reverseAPIDeviceIndex = u32tmp > 99 ? 99 : u32tmp; | ||||
|         d.readU32(27, &u32tmp, 0); | ||||
|         m_reverseAPIChannelIndex = u32tmp > 99 ? 99 : u32tmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|  |  | |||
|  | @ -62,6 +62,12 @@ struct UDPSinkSettings | |||
| 
 | ||||
|     QString m_title; | ||||
| 
 | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
|     Serializable *m_spectrumGUI; | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,6 +20,9 @@ | |||
| #include <boost/cstdint.hpp> | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
|  | @ -59,10 +62,15 @@ DaemonSource::DaemonSource(DeviceSinkAPI *deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSource(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| DaemonSource::~DaemonSource() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
|     m_deviceAPI->removeChannelAPI(this); | ||||
|     m_deviceAPI->removeThreadedSource(m_threadedChannelizer); | ||||
|     delete m_threadedChannelizer; | ||||
|  | @ -201,19 +209,36 @@ void DaemonSource::applySettings(const DaemonSourceSettings& settings, bool forc | |||
|             << " force: " << force; | ||||
| 
 | ||||
|     bool change = false; | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { | ||||
|     if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("dataAddress"); | ||||
|         change = true; | ||||
|     } | ||||
| 
 | ||||
|     if ((m_settings.m_dataPort != settings.m_dataPort) || force) { | ||||
|     if ((m_settings.m_dataPort != settings.m_dataPort) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("dataPort"); | ||||
|         change = true; | ||||
|     } | ||||
| 
 | ||||
|     if (change && m_sourceThread) { | ||||
|     if (change && m_sourceThread) | ||||
|     { | ||||
|         reverseAPIKeys.append("sourceThread"); | ||||
|         m_sourceThread->dataBind(settings.m_dataAddress, settings.m_dataPort); | ||||
|     } | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -392,6 +417,21 @@ int DaemonSource::webapiSettingsPutPatch( | |||
|     if (channelSettingsKeys.contains("title")) { | ||||
|         settings.m_title = *response.getDaemonSourceSettings()->getTitle(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("useReverseAPI")) { | ||||
|         settings.m_useReverseAPI = response.getDaemonSourceSettings()->getUseReverseApi() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIAddress")) { | ||||
|         settings.m_reverseAPIAddress = *response.getDaemonSourceSettings()->getReverseApiAddress() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIPort")) { | ||||
|         settings.m_reverseAPIPort = response.getDaemonSourceSettings()->getReverseApiPort(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { | ||||
|         settings.m_reverseAPIDeviceIndex = response.getDaemonSourceSettings()->getReverseApiDeviceIndex(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { | ||||
|         settings.m_reverseAPIChannelIndex = response.getDaemonSourceSettings()->getReverseApiChannelIndex(); | ||||
|     } | ||||
| 
 | ||||
|     MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(settings, force); | ||||
|     m_inputMessageQueue.push(msg); | ||||
|  | @ -435,6 +475,18 @@ void DaemonSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& | |||
|     } else { | ||||
|         response.getDaemonSourceSettings()->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
| 
 | ||||
|     response.getDaemonSourceSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); | ||||
| 
 | ||||
|     if (response.getDaemonSourceSettings()->getReverseApiAddress()) { | ||||
|         *response.getDaemonSourceSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; | ||||
|     } else { | ||||
|         response.getDaemonSourceSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); | ||||
|     } | ||||
| 
 | ||||
|     response.getDaemonSourceSettings()->setReverseApiPort(settings.m_reverseAPIPort); | ||||
|     response.getDaemonSourceSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); | ||||
|     response.getDaemonSourceSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); | ||||
| } | ||||
| 
 | ||||
| void DaemonSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | ||||
|  | @ -457,3 +509,62 @@ void DaemonSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& resp | |||
|     response.getDaemonSourceReport()->setDeviceSampleRate(m_deviceAPI->getSampleSink()->getSampleRate()); | ||||
| } | ||||
| 
 | ||||
| void DaemonSource::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DaemonSourceSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(1); | ||||
|     swgChannelSettings->setChannelType(new QString("DaemonSource")); | ||||
|     swgChannelSettings->setDaemonSourceSettings(new SWGSDRangel::SWGDaemonSourceSettings()); | ||||
|     SWGSDRangel::SWGDaemonSourceSettings *swgDaemonSourceSettings = swgChannelSettings->getDaemonSourceSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("dataAddress") || force) { | ||||
|         swgDaemonSourceSettings->setDataAddress(new QString(settings.m_dataAddress)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("dataPort") || force) { | ||||
|         swgDaemonSourceSettings->setDataPort(settings.m_dataPort); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgDaemonSourceSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgDaemonSourceSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void DaemonSource::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "DaemonSource::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("DaemonSource::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| #define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "cm256.h" | ||||
| 
 | ||||
|  | @ -35,6 +36,8 @@ class UpChannelizer; | |||
| class DeviceSinkAPI; | ||||
| class DaemonSourceThread; | ||||
| class SDRDaemonDataBlock; | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| 
 | ||||
| class DaemonSource : public BasebandSampleSource, public ChannelSourceAPI { | ||||
|     Q_OBJECT | ||||
|  | @ -233,14 +236,19 @@ private: | |||
|     uint32_t m_nbCorrectableErrors;   //!< count of correctable errors in number of blocks
 | ||||
|     uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks
 | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     void applySettings(const DaemonSourceSettings& settings, bool force = false); | ||||
|     void handleDataBlock(SDRDaemonDataBlock *dataBlock); | ||||
|     void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); | ||||
|     uint32_t calculateDataReadQueueSize(int sampleRate); | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSourceSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DaemonSourceSettings& settings, bool force); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
|     void handleData(); | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -273,11 +273,22 @@ void DaemonSourceGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void DaemonSourceGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ | |||
| 
 | ||||
| const PluginDescriptor DaemonSourcePlugin::m_pluginDescriptor = { | ||||
|     QString("Daemon channel source"), | ||||
|     QString("4.1.0"), | ||||
|     QString("4.3.2"), | ||||
|     QString("(c) Edouard Griffiths, F4EXB"), | ||||
|     QString("https://github.com/f4exb/sdrangel"), | ||||
|     true, | ||||
|  |  | |||
|  | @ -32,6 +32,11 @@ void DaemonSourceSettings::resetToDefaults() | |||
|     m_rgbColor = QColor(140, 4, 4).rgb(); | ||||
|     m_title = "Daemon source"; | ||||
|     m_channelMarker = nullptr; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray DaemonSourceSettings::serialize() const | ||||
|  | @ -41,6 +46,11 @@ QByteArray DaemonSourceSettings::serialize() const | |||
|     s.writeU32(2, m_dataPort); | ||||
|     s.writeU32(3, m_rgbColor); | ||||
|     s.writeString(4, m_title); | ||||
|     s.writeBool(5, m_useReverseAPI); | ||||
|     s.writeString(6, m_reverseAPIAddress); | ||||
|     s.writeU32(7, m_reverseAPIPort); | ||||
|     s.writeU32(8, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(9, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -71,7 +81,20 @@ bool DaemonSourceSettings::deserialize(const QByteArray& data) | |||
| 
 | ||||
|         d.readU32(3, &m_rgbColor, QColor(0, 255, 255).rgb()); | ||||
|         d.readString(4, &m_title, "Daemon source"); | ||||
|         d.readBool(5, &m_useReverseAPI, false); | ||||
|         d.readString(6, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(7, &tmp, 0); | ||||
| 
 | ||||
|         if ((tmp > 1023) && (tmp < 65535)) { | ||||
|             m_reverseAPIPort = tmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(8, &tmp, 0); | ||||
|         m_reverseAPIDeviceIndex = tmp > 99 ? 99 : tmp; | ||||
|         d.readU32(9, &tmp, 0); | ||||
|         m_reverseAPIChannelIndex = tmp > 99 ? 99 : tmp; | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|  |  | |||
|  | @ -28,6 +28,11 @@ struct DaemonSourceSettings | |||
|     uint16_t m_dataPort;    //!< Listening data port
 | ||||
|     quint32 m_rgbColor; | ||||
|     QString m_title; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,19 +14,22 @@ | |||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include "ammod.h" | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
| 
 | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| #include <QMutexLocker> | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <complex.h> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
| #include "SWGAMModReport.h" | ||||
| 
 | ||||
| #include "ammod.h" | ||||
| 
 | ||||
| #include "dsp/upchannelizer.h" | ||||
| #include "dsp/dspengine.h" | ||||
| #include "dsp/threadedbasebandsamplesource.h" | ||||
|  | @ -84,10 +87,15 @@ AMMod::AMMod(DeviceSinkAPI *deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSource(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| AMMod::~AMMod() | ||||
| { | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
|     m_deviceAPI->removeChannelAPI(this); | ||||
|     m_deviceAPI->removeThreadedSource(m_threadedChannelizer); | ||||
|     delete m_threadedChannelizer; | ||||
|  | @ -332,6 +340,16 @@ bool AMMod::handleMessage(const Message& cmd) | |||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (CWKeyer::MsgConfigureCWKeyer::match(cmd)) | ||||
|     { | ||||
|         const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd; | ||||
| 
 | ||||
|         if (m_settings.m_useReverseAPI) { | ||||
|             webapiReverseSendCWSettings(cfg.getSettings()); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (DSPConfigureAudio::match(cmd)) | ||||
|     { | ||||
|         DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; | ||||
|  | @ -451,14 +469,46 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) | |||
|             << " m_modFactor: " << settings.m_modFactor | ||||
|             << " m_toneFrequency: " << settings.m_toneFrequency | ||||
|             << " m_volumeFactor: " << settings.m_volumeFactor | ||||
|             << " m_audioMute: " << settings.m_channelMute | ||||
|             << " m_channelMute: " << settings.m_channelMute | ||||
|             << " m_playLoop: " << settings.m_playLoop | ||||
|             << " m_modAFInput " << settings.m_modAFInput | ||||
|             << " m_audioDeviceName: " << settings.m_audioDeviceName | ||||
|             << " m_useReverseAPI: " << settings.m_useReverseAPI | ||||
|             << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress | ||||
|             << " m_reverseAPIAddress: " << settings.m_reverseAPIPort | ||||
|             << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex | ||||
|             << " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_modFactor != m_settings.m_modFactor) || force) { | ||||
|         reverseAPIKeys.append("modFactor"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_volumeFactor != m_settings.m_volumeFactor) || force) { | ||||
|         reverseAPIKeys.append("volumeFactor"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_channelMute != m_settings.m_channelMute) || force) { | ||||
|         reverseAPIKeys.append("channelMute"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_playLoop != m_settings.m_playLoop) || force) { | ||||
|         reverseAPIKeys.append("playLoop"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_modAFInput != m_settings.m_modAFInput) || force) { | ||||
|         reverseAPIKeys.append("modAFInput"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|         m_settingsMutex.lock(); | ||||
|         m_interpolatorDistanceRemain = 0; | ||||
|         m_interpolatorConsumed = false; | ||||
|  | @ -469,6 +519,7 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) | |||
| 
 | ||||
|     if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("toneFrequency"); | ||||
|         m_settingsMutex.lock(); | ||||
|         m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); | ||||
|         m_settingsMutex.unlock(); | ||||
|  | @ -476,16 +527,28 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) | |||
| 
 | ||||
|     if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) | ||||
|     { | ||||
|         reverseAPIKeys.append("audioDeviceName"); | ||||
|         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); | ||||
|         int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); | ||||
|         audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); | ||||
|         uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); | ||||
| 
 | ||||
|         if (m_audioSampleRate != audioSampleRate) { | ||||
|             reverseAPIKeys.append("audioSampleRate"); | ||||
|             applyAudioSampleRate(audioSampleRate); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -567,6 +630,21 @@ int AMMod::webapiSettingsPutPatch( | |||
|     if (channelSettingsKeys.contains("modFactor")) { | ||||
|         settings.m_modFactor = response.getAmModSettings()->getModFactor(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("useReverseAPI")) { | ||||
|         settings.m_useReverseAPI = response.getAmModSettings()->getUseReverseApi() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIAddress")) { | ||||
|         settings.m_reverseAPIAddress = *response.getAmModSettings()->getReverseApiAddress() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIPort")) { | ||||
|         settings.m_reverseAPIPort = response.getAmModSettings()->getReverseApiPort(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { | ||||
|         settings.m_reverseAPIDeviceIndex = response.getAmModSettings()->getReverseApiDeviceIndex(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { | ||||
|         settings.m_reverseAPIChannelIndex = response.getAmModSettings()->getReverseApiChannelIndex(); | ||||
|     } | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("cwKeyer")) | ||||
|     { | ||||
|  | @ -676,6 +754,18 @@ void AMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respons | |||
|     } else { | ||||
|         response.getAmModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
| 
 | ||||
|     response.getAmModSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); | ||||
| 
 | ||||
|     if (response.getAmModSettings()->getReverseApiAddress()) { | ||||
|         *response.getAmModSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; | ||||
|     } else { | ||||
|         response.getAmModSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); | ||||
|     } | ||||
| 
 | ||||
|     response.getAmModSettings()->setReverseApiPort(settings.m_reverseAPIPort); | ||||
|     response.getAmModSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); | ||||
|     response.getAmModSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); | ||||
| } | ||||
| 
 | ||||
| void AMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | ||||
|  | @ -684,3 +774,131 @@ void AMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | |||
|     response.getAmModReport()->setAudioSampleRate(m_audioSampleRate); | ||||
|     response.getAmModReport()->setChannelSampleRate(m_outputSampleRate); | ||||
| } | ||||
| 
 | ||||
| void AMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMModSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(1); | ||||
|     swgChannelSettings->setChannelType(new QString("AMMod")); | ||||
|     swgChannelSettings->setAmModSettings(new SWGSDRangel::SWGAMModSettings()); | ||||
|     SWGSDRangel::SWGAMModSettings *swgAMModSettings = swgChannelSettings->getAmModSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("channelMute") || force) { | ||||
|         swgAMModSettings->setChannelMute(settings.m_channelMute ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgAMModSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("modAFInput") || force) { | ||||
|         swgAMModSettings->setModAfInput((int) settings.m_modAFInput); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("audioDeviceName") || force) { | ||||
|         swgAMModSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("playLoop") || force) { | ||||
|         swgAMModSettings->setPlayLoop(settings.m_playLoop ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgAMModSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgAMModSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgAMModSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("toneFrequency") || force) { | ||||
|         swgAMModSettings->setToneFrequency(settings.m_toneFrequency); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("volumeFactor") || force) { | ||||
|         swgAMModSettings->setVolumeFactor(settings.m_volumeFactor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("modFactor") || force) { | ||||
|         swgAMModSettings->setModFactor(settings.m_modFactor); | ||||
|     } | ||||
| 
 | ||||
|     if (force) | ||||
|     { | ||||
|         const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); | ||||
|         swgAMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings()); | ||||
|         SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgAMModSettings->getCwKeyer(); | ||||
|         apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); | ||||
|         apiCwKeyerSettings->setMode(cwKeyerSettings.m_mode); | ||||
|         apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); | ||||
|         apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); | ||||
|         apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void AMMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(1); | ||||
|     swgChannelSettings->setChannelType(new QString("AMMod")); | ||||
|     swgChannelSettings->setAmModSettings(new SWGSDRangel::SWGAMModSettings()); | ||||
|     SWGSDRangel::SWGAMModSettings *swgAMModSettings = swgChannelSettings->getAmModSettings(); | ||||
| 
 | ||||
|     swgAMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings()); | ||||
|     SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgAMModSettings->getCwKeyer(); | ||||
|     apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); | ||||
|     apiCwKeyerSettings->setMode(cwKeyerSettings.m_mode); | ||||
|     apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); | ||||
|     apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); | ||||
|     apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(m_settings.m_reverseAPIAddress) | ||||
|             .arg(m_settings.m_reverseAPIPort) | ||||
|             .arg(m_settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(m_settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void AMMod::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "AMMod::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("AMMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -17,11 +17,13 @@ | |||
| #ifndef PLUGINS_CHANNELTX_MODAM_AMMOD_H_ | ||||
| #define PLUGINS_CHANNELTX_MODAM_AMMOD_H_ | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <vector> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesource.h" | ||||
| #include "channel/channelsourceapi.h" | ||||
| #include "dsp/nco.h" | ||||
|  | @ -35,6 +37,8 @@ | |||
| 
 | ||||
| #include "ammodsettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class ThreadedBasebandSampleSource; | ||||
| class UpChannelizer; | ||||
| class DeviceSinkAPI; | ||||
|  | @ -291,6 +295,9 @@ private: | |||
| 
 | ||||
|     static const int m_levelNbSamples; | ||||
| 
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     void applyAudioSampleRate(int sampleRate); | ||||
|     void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); | ||||
|     void applySettings(const AMModSettings& settings, bool force = false); | ||||
|  | @ -301,6 +308,11 @@ private: | |||
|     void seekFileStream(int seekPercentage); | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMModSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMModSettings& settings, bool force); | ||||
|     void webapiReverseSendCWSettings(const CWKeyerSettings& settings); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -279,12 +279,22 @@ void AMModGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void AMModGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  | @ -316,7 +326,7 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampl | |||
| 	connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); | ||||
| 
 | ||||
|     CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); | ||||
|     connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); | ||||
| 
 | ||||
|     ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); | ||||
|     ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ | |||
| 
 | ||||
| const PluginDescriptor AMModPlugin::m_pluginDescriptor = { | ||||
|     QString("AM Modulator"), | ||||
|     QString("4.1.0"), | ||||
|     QString("4.3.2"), | ||||
|     QString("(c) Edouard Griffiths, F4EXB"), | ||||
|     QString("https://github.com/f4exb/sdrangel"), | ||||
|     true, | ||||
|  |  | |||
|  | @ -41,6 +41,11 @@ void AMModSettings::resetToDefaults() | |||
|     m_title = "AM Modulator"; | ||||
|     m_modAFInput = AMModInputAF::AMModInputNone; | ||||
|     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray AMModSettings::serialize() const | ||||
|  | @ -65,6 +70,11 @@ QByteArray AMModSettings::serialize() const | |||
|     s.writeString(9, m_title); | ||||
|     s.writeString(10, m_audioDeviceName); | ||||
|     s.writeS32(11, (int) m_modAFInput); | ||||
|     s.writeBool(12, m_useReverseAPI); | ||||
|     s.writeString(13, m_reverseAPIAddress); | ||||
|     s.writeU32(14, m_reverseAPIPort); | ||||
|     s.writeU32(15, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(16, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -83,6 +93,7 @@ bool AMModSettings::deserialize(const QByteArray& data) | |||
|     { | ||||
|         QByteArray bytetmp; | ||||
|         qint32 tmp; | ||||
|         uint32_t utmp; | ||||
| 
 | ||||
|         d.readS32(1, &tmp, 0); | ||||
|         m_inputFrequencyOffset = tmp; | ||||
|  | @ -112,6 +123,21 @@ bool AMModSettings::deserialize(const QByteArray& data) | |||
|             m_modAFInput = (AMModInputAF) tmp; | ||||
|         } | ||||
| 
 | ||||
|         d.readBool(12, &m_useReverseAPI, false); | ||||
|         d.readString(13, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(14, &utmp, 0); | ||||
| 
 | ||||
|         if ((utmp > 1023) && (utmp < 65535)) { | ||||
|             m_reverseAPIPort = utmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(15, &utmp, 0); | ||||
|         m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; | ||||
|         d.readU32(16, &utmp, 0); | ||||
|         m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|  |  | |||
|  | @ -43,6 +43,11 @@ struct AMModSettings | |||
|     QString m_title; | ||||
|     AMModInputAF m_modAFInput; | ||||
|     QString m_audioDeviceName; | ||||
|     bool m_useReverseAPI; | ||||
|     QString m_reverseAPIAddress; | ||||
|     uint16_t m_reverseAPIPort; | ||||
|     uint16_t m_reverseAPIDeviceIndex; | ||||
|     uint16_t m_reverseAPIChannelIndex; | ||||
| 
 | ||||
|     Serializable *m_channelMarker; | ||||
|     Serializable *m_cwKeyerGUI; | ||||
|  |  | |||
|  | @ -14,9 +14,13 @@ | |||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <time.h> | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QNetworkReply> | ||||
| #include <QBuffer> | ||||
| 
 | ||||
| #include "SWGChannelSettings.h" | ||||
| #include "SWGChannelReport.h" | ||||
| #include "SWGATVModReport.h" | ||||
|  | @ -100,12 +104,21 @@ ATVMod::ATVMod(DeviceSinkAPI *deviceAPI) : | |||
|     m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); | ||||
|     m_deviceAPI->addThreadedSource(m_threadedChannelizer); | ||||
|     m_deviceAPI->addChannelAPI(this); | ||||
| 
 | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
| } | ||||
| 
 | ||||
| ATVMod::~ATVMod() | ||||
| { | ||||
| 	if (m_video.isOpened()) m_video.release(); | ||||
| 	releaseCameras(); | ||||
|     disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); | ||||
|     delete m_networkManager; | ||||
| 
 | ||||
|     if (m_video.isOpened()) { | ||||
| 	    m_video.release(); | ||||
| 	} | ||||
| 
 | ||||
|     releaseCameras(); | ||||
| 	m_deviceAPI->removeChannelAPI(this); | ||||
|     m_deviceAPI->removeThreadedSource(m_threadedChannelizer); | ||||
|     delete m_threadedChannelizer; | ||||
|  | @ -1090,8 +1103,72 @@ void ATVMod::applySettings(const ATVModSettings& settings, bool force) | |||
|             << " m_fmExcursion: " << settings.m_fmExcursion | ||||
|             << " m_forceDecimator: " << settings.m_forceDecimator | ||||
|             << " m_showOverlayText: " << settings.m_showOverlayText | ||||
|             << " m_overlayText: " << settings.m_overlayText | ||||
|             << " force: " << force; | ||||
| 
 | ||||
|     QList<QString> reverseAPIKeys; | ||||
| 
 | ||||
|     if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { | ||||
|         reverseAPIKeys.append("inputFrequencyOffset"); | ||||
|     } | ||||
|     if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { | ||||
|         reverseAPIKeys.append("rfBandwidth"); | ||||
|     } | ||||
|     if ((settings.m_rfOppBandwidth != m_settings.m_rfOppBandwidth) || force) { | ||||
|         reverseAPIKeys.append("rfOppBandwidth"); | ||||
|     } | ||||
|     if ((settings.m_atvStd != m_settings.m_atvStd) || force) { | ||||
|         reverseAPIKeys.append("atvStd"); | ||||
|     } | ||||
|     if ((settings.m_nbLines != m_settings.m_nbLines) || force) { | ||||
|         reverseAPIKeys.append("nbLines"); | ||||
|     } | ||||
|     if ((settings.m_fps != m_settings.m_fps) || force) { | ||||
|         reverseAPIKeys.append("fps"); | ||||
|     } | ||||
|     if ((settings.m_atvModInput != m_settings.m_atvModInput) || force) { | ||||
|         reverseAPIKeys.append("atvModInput"); | ||||
|     } | ||||
|     if ((settings.m_uniformLevel != m_settings.m_uniformLevel) || force) { | ||||
|         reverseAPIKeys.append("uniformLevel"); | ||||
|     } | ||||
|     if ((settings.m_uniformLevel != m_settings.m_uniformLevel) || force) { | ||||
|         reverseAPIKeys.append("uniformLevel"); | ||||
|     } | ||||
|     if ((settings.m_atvModulation != m_settings.m_atvModulation) || force) { | ||||
|         reverseAPIKeys.append("atvModulation"); | ||||
|     } | ||||
|     if ((settings.m_videoPlayLoop != m_settings.m_videoPlayLoop) || force) { | ||||
|         reverseAPIKeys.append("videoPlayLoop"); | ||||
|     } | ||||
|     if ((settings.m_videoPlay != m_settings.m_videoPlay) || force) { | ||||
|         reverseAPIKeys.append("videoPlay"); | ||||
|     } | ||||
|     if ((settings.m_cameraPlay != m_settings.m_cameraPlay) || force) { | ||||
|         reverseAPIKeys.append("cameraPlay"); | ||||
|     } | ||||
|     if ((settings.m_channelMute != m_settings.m_channelMute) || force) { | ||||
|         reverseAPIKeys.append("channelMute"); | ||||
|     } | ||||
|     if ((settings.m_invertedVideo != m_settings.m_invertedVideo) || force) { | ||||
|         reverseAPIKeys.append("invertedVideo"); | ||||
|     } | ||||
|     if ((settings.m_rfScalingFactor != m_settings.m_rfScalingFactor) || force) { | ||||
|         reverseAPIKeys.append("rfScalingFactor"); | ||||
|     } | ||||
|     if ((settings.m_fmExcursion != m_settings.m_fmExcursion) || force) { | ||||
|         reverseAPIKeys.append("fmExcursion"); | ||||
|     } | ||||
|     if ((settings.m_forceDecimator != m_settings.m_forceDecimator) || force) { | ||||
|         reverseAPIKeys.append("forceDecimator"); | ||||
|     } | ||||
|     if ((settings.m_showOverlayText != m_settings.m_showOverlayText) || force) { | ||||
|         reverseAPIKeys.append("showOverlayText"); | ||||
|     } | ||||
|     if ((settings.m_overlayText != m_settings.m_overlayText) || force) { | ||||
|         reverseAPIKeys.append("overlayText"); | ||||
|     } | ||||
| 
 | ||||
|     if ((settings.m_atvStd != m_settings.m_atvStd) | ||||
|         || (settings.m_nbLines != m_settings.m_nbLines) | ||||
|         || (settings.m_fps != m_settings.m_fps) | ||||
|  | @ -1159,6 +1236,16 @@ void ATVMod::applySettings(const ATVModSettings& settings, bool force) | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (settings.m_useReverseAPI) | ||||
|     { | ||||
|         bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || | ||||
|                 (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || | ||||
|                 (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || | ||||
|                 (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || | ||||
|                 (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); | ||||
|         webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); | ||||
|     } | ||||
| 
 | ||||
|     m_settings = settings; | ||||
| } | ||||
| 
 | ||||
|  | @ -1214,7 +1301,7 @@ int ATVMod::webapiSettingsPutPatch( | |||
|         settings.m_rfBandwidth = response.getAtvModSettings()->getRfBandwidth(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfOppBandwidth")) { | ||||
|         settings.m_rfOppBandwidth = response.getAtvModSettings()->getRfBandwidth(); | ||||
|         settings.m_rfOppBandwidth = response.getAtvModSettings()->getRfOppBandwidth(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("atvStd")) { | ||||
|         settings.m_atvStd = (ATVModSettings::ATVStd) response.getAtvModSettings()->getAtvStd(); | ||||
|  | @ -1270,7 +1357,21 @@ int ATVMod::webapiSettingsPutPatch( | |||
|     if (channelSettingsKeys.contains("title")) { | ||||
|         settings.m_title = *response.getAtvModSettings()->getTitle(); | ||||
|     } | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("useReverseAPI")) { | ||||
|         settings.m_useReverseAPI = response.getAtvModSettings()->getUseReverseApi() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIAddress")) { | ||||
|         settings.m_reverseAPIAddress = *response.getAtvModSettings()->getReverseApiAddress() != 0; | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIPort")) { | ||||
|         settings.m_reverseAPIPort = response.getAtvModSettings()->getReverseApiPort(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { | ||||
|         settings.m_reverseAPIDeviceIndex = response.getAtvModSettings()->getReverseApiDeviceIndex(); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { | ||||
|         settings.m_reverseAPIChannelIndex = response.getAtvModSettings()->getReverseApiChannelIndex(); | ||||
|     } | ||||
|     if (frequencyOffsetChanged) | ||||
|     { | ||||
|         ATVMod::MsgConfigureChannelizer *msgChan = ATVMod::MsgConfigureChannelizer::create( | ||||
|  | @ -1377,6 +1478,18 @@ void ATVMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon | |||
|     } else { | ||||
|         response.getAtvModSettings()->setVideoFileName(new QString(m_videoFileName)); | ||||
|     } | ||||
| 
 | ||||
|     response.getAtvModSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); | ||||
| 
 | ||||
|     if (response.getAtvModSettings()->getReverseApiAddress()) { | ||||
|         *response.getAtvModSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; | ||||
|     } else { | ||||
|         response.getAtvModSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); | ||||
|     } | ||||
| 
 | ||||
|     response.getAtvModSettings()->setReverseApiPort(settings.m_reverseAPIPort); | ||||
|     response.getAtvModSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); | ||||
|     response.getAtvModSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); | ||||
| } | ||||
| 
 | ||||
| void ATVMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | ||||
|  | @ -1384,3 +1497,114 @@ void ATVMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) | |||
|     response.getAtvModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); | ||||
|     response.getAtvModReport()->setChannelSampleRate(m_outputSampleRate); | ||||
| } | ||||
| 
 | ||||
| void ATVMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const ATVModSettings& settings, bool force) | ||||
| { | ||||
|     SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); | ||||
|     swgChannelSettings->setTx(1); | ||||
|     swgChannelSettings->setChannelType(new QString("ATVMod")); | ||||
|     swgChannelSettings->setAtvModSettings(new SWGSDRangel::SWGATVModSettings()); | ||||
|     SWGSDRangel::SWGATVModSettings *swgATVModSettings = swgChannelSettings->getAtvModSettings(); | ||||
| 
 | ||||
|     // transfer data that has been modified. When force is on transfer all data except reverse API data
 | ||||
| 
 | ||||
|     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { | ||||
|         swgATVModSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfBandwidth") || force) { | ||||
|         swgATVModSettings->setRfBandwidth(settings.m_rfBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfOppBandwidth") || force) { | ||||
|         swgATVModSettings->setRfOppBandwidth(settings.m_rfOppBandwidth); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("atvStd") || force) { | ||||
|         swgATVModSettings->setAtvStd((int) settings.m_atvStd); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("nbLines") || force) { | ||||
|         swgATVModSettings->setNbLines(settings.m_nbLines); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("fps") || force) { | ||||
|         swgATVModSettings->setFps(settings.m_fps); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("atvModInput") || force) { | ||||
|         swgATVModSettings->setAtvModInput((int) settings.m_atvModInput); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("uniformLevel") || force) { | ||||
|         swgATVModSettings->setUniformLevel(settings.m_uniformLevel); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("atvModulation") || force) { | ||||
|         swgATVModSettings->setAtvModulation((int) settings.m_atvModulation); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("videoPlayLoop") || force) { | ||||
|         swgATVModSettings->setVideoPlayLoop(settings.m_videoPlayLoop ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("videoPlay") || force) { | ||||
|         swgATVModSettings->setVideoPlay(settings.m_videoPlay ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("cameraPlay") || force) { | ||||
|         swgATVModSettings->setCameraPlay(settings.m_cameraPlay ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("channelMute") || force) { | ||||
|         swgATVModSettings->setChannelMute(settings.m_channelMute ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("invertedVideo") || force) { | ||||
|         swgATVModSettings->setInvertedVideo(settings.m_invertedVideo ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rfScalingFactor") || force) { | ||||
|         swgATVModSettings->setRfScalingFactor(settings.m_rfScalingFactor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("fmExcursion") || force) { | ||||
|         swgATVModSettings->setFmExcursion(settings.m_fmExcursion); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("forceDecimator") || force) { | ||||
|         swgATVModSettings->setForceDecimator(settings.m_forceDecimator ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("showOverlayText") || force) { | ||||
|         swgATVModSettings->setShowOverlayText(settings.m_showOverlayText ? 1 : 0); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("overlayText") || force) { | ||||
|         swgATVModSettings->setOverlayText(new QString(settings.m_overlayText)); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("rgbColor") || force) { | ||||
|         swgATVModSettings->setRgbColor(settings.m_rgbColor); | ||||
|     } | ||||
|     if (channelSettingsKeys.contains("title") || force) { | ||||
|         swgATVModSettings->setTitle(new QString(settings.m_title)); | ||||
|     } | ||||
| 
 | ||||
|     QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") | ||||
|             .arg(settings.m_reverseAPIAddress) | ||||
|             .arg(settings.m_reverseAPIPort) | ||||
|             .arg(settings.m_reverseAPIDeviceIndex) | ||||
|             .arg(settings.m_reverseAPIChannelIndex); | ||||
|     m_networkRequest.setUrl(QUrl(channelSettingsURL)); | ||||
|     m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QBuffer *buffer=new QBuffer(); | ||||
|     buffer->open((QBuffer::ReadWrite)); | ||||
|     buffer->write(swgChannelSettings->asJson().toUtf8()); | ||||
|     buffer->seek(0); | ||||
| 
 | ||||
|     // Always use PATCH to avoid passing reverse API settings
 | ||||
|     m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); | ||||
| 
 | ||||
|     delete swgChannelSettings; | ||||
| } | ||||
| 
 | ||||
| void ATVMod::networkManagerFinished(QNetworkReply *reply) | ||||
| { | ||||
|     QNetworkReply::NetworkError replyError = reply->error(); | ||||
| 
 | ||||
|     if (replyError) | ||||
|     { | ||||
|         qWarning() << "ATVMod::networkManagerFinished:" | ||||
|                 << " error(" << (int) replyError | ||||
|                 << "): " << replyError | ||||
|                 << ": " << reply->errorString(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString answer = reply->readAll(); | ||||
|     answer.chop(1); // remove last \n
 | ||||
|     qDebug("ATVMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); | ||||
| } | ||||
|  |  | |||
|  | @ -17,10 +17,11 @@ | |||
| #ifndef PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ | ||||
| #define PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QMutex> | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <QNetworkRequest> | ||||
| 
 | ||||
| #include <opencv2/core/core.hpp> | ||||
| #include <opencv2/highgui/highgui.hpp> | ||||
|  | @ -37,6 +38,8 @@ | |||
| 
 | ||||
| #include "atvmodsettings.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| class DeviceSinkAPI; | ||||
| class ThreadedBasebandSampleSource; | ||||
| class UpChannelizer; | ||||
|  | @ -400,6 +403,9 @@ signals: | |||
|      */ | ||||
|     void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples); | ||||
| 
 | ||||
| private slots: | ||||
|     void networkManagerFinished(QNetworkReply *reply); | ||||
| 
 | ||||
| private: | ||||
|     struct ATVCamera | ||||
|     { | ||||
|  | @ -524,8 +530,10 @@ private: | |||
|     Complex* m_DSBFilterBuffer; | ||||
|     int m_DSBFilterBufferIndex; | ||||
| 
 | ||||
|     static const int m_ssbFftLen; | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
|     QNetworkRequest m_networkRequest; | ||||
| 
 | ||||
|     static const int m_ssbFftLen; | ||||
|     static const float m_blackLevel; | ||||
|     static const float m_spanLevel; | ||||
|     static const int m_levelNbSamples; | ||||
|  | @ -556,6 +564,7 @@ private: | |||
| 
 | ||||
|     void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const ATVModSettings& settings); | ||||
|     void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); | ||||
|     void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const ATVModSettings& settings, bool force); | ||||
| 
 | ||||
|     inline void pullImageLine(Real& sample, bool noHSync = false) | ||||
|     { | ||||
|  |  | |||
|  | @ -207,8 +207,8 @@ void ATVModGUI::setRFFiltersSlidersRange(int sampleRate) | |||
|         ui->rfOppBW->setMaximum((sampleRate) / m_rfSliderDivisor); | ||||
|     } | ||||
| 
 | ||||
|     ui->rfBWText->setText(QString("%1k").arg((ui->rfBW->value()*m_rfSliderDivisor) / 1000.0, 0, 'f', 0)); | ||||
|     ui->rfOppBWText->setText(QString("%1k").arg((ui->rfOppBW->value()*m_rfSliderDivisor) / 1000.0, 0, 'f', 0)); | ||||
|     ui->rfBWText->setText(QString("%1k").arg((ui->rfBW->value()*m_rfSliderDivisor) / 1000.0, 0, 'f', 1)); | ||||
|     ui->rfOppBWText->setText(QString("%1k").arg((ui->rfOppBW->value()*m_rfSliderDivisor) / 1000.0, 0, 'f', 1)); | ||||
| } | ||||
| 
 | ||||
| int ATVModGUI::getNbLines() | ||||
|  | @ -372,6 +372,7 @@ void ATVModGUI::handleSourceMessages() | |||
| void ATVModGUI::on_deltaFrequency_changed(qint64 value) | ||||
| { | ||||
|     m_channelMarker.setCenterFrequency(value); | ||||
|     m_settings.m_inputFrequencyOffset = value; | ||||
|     applySettings(); | ||||
| } | ||||
| 
 | ||||
|  | @ -399,7 +400,7 @@ void ATVModGUI::on_fmExcursion_valueChanged(int value) | |||
| 
 | ||||
| void ATVModGUI::on_rfBW_valueChanged(int value) | ||||
| { | ||||
| 	ui->rfBWText->setText(QString("%1k").arg((value*m_rfSliderDivisor) / 1000.0, 0, 'f', 0)); | ||||
| 	ui->rfBWText->setText(QString("%1k").arg((value*m_rfSliderDivisor) / 1000.0, 0, 'f', 1)); | ||||
| 	m_settings.m_rfBandwidth = value * m_rfSliderDivisor * 1.0f; | ||||
| 	setChannelMarkerBandwidth(); | ||||
| 	applySettings(); | ||||
|  | @ -407,7 +408,7 @@ void ATVModGUI::on_rfBW_valueChanged(int value) | |||
| 
 | ||||
| void ATVModGUI::on_rfOppBW_valueChanged(int value) | ||||
| { | ||||
|     ui->rfOppBWText->setText(QString("%1k").arg((value*m_rfSliderDivisor) / 1000.0, 0, 'f', 0)); | ||||
|     ui->rfOppBWText->setText(QString("%1k").arg((value*m_rfSliderDivisor) / 1000.0, 0, 'f', 1)); | ||||
|     m_settings.m_rfOppBandwidth = value * m_rfSliderDivisor * 1.0f; | ||||
|     setChannelMarkerBandwidth(); | ||||
|     applySettings(); | ||||
|  | @ -614,12 +615,23 @@ void ATVModGUI::onWidgetRolled(QWidget* widget, bool rollDown) | |||
| void ATVModGUI::onMenuDialogCalled(const QPoint &p) | ||||
| { | ||||
|     BasicChannelSettingsDialog dialog(&m_channelMarker, this); | ||||
|     dialog.setUseReverseAPI(m_settings.m_useReverseAPI); | ||||
|     dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); | ||||
|     dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); | ||||
|     dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); | ||||
|     dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     dialog.move(p); | ||||
|     dialog.exec(); | ||||
| 
 | ||||
|     m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); | ||||
|     m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); | ||||
|     m_settings.m_title = m_channelMarker.getTitle(); | ||||
|     m_settings.m_useReverseAPI = dialog.useReverseAPI(); | ||||
|     m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); | ||||
|     m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); | ||||
|     m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); | ||||
|     m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); | ||||
| 
 | ||||
|     setWindowTitle(m_settings.m_title); | ||||
|     setTitleColor(m_settings.m_rgbColor); | ||||
|  | @ -737,11 +749,10 @@ void ATVModGUI::displaySettings() | |||
|     setRFFiltersSlidersRange(m_atvMod->getEffectiveSampleRate()); | ||||
| 
 | ||||
|     ui->rfBW->setValue(roundf(m_settings.m_rfBandwidth / m_rfSliderDivisor)); | ||||
|     ui->rfBWText->setText(QString("%1k").arg((ui->rfBW->value()*m_rfSliderDivisor) / 1000.0, 0, 'f', 0)); | ||||
|     ui->rfBWText->setText(QString("%1k").arg((ui->rfBW->value()*m_rfSliderDivisor) / 1000.0, 0, 'f', 1)); | ||||
| 
 | ||||
|     ui->rfOppBW->setValue(roundf(m_settings.m_rfOppBandwidth / m_rfSliderDivisor)); | ||||
|     ui->rfOppBWText->setText(QString("%1k").arg((ui->rfOppBW->value()*m_rfSliderDivisor) / 1000.0, 0, 'f', 0)); | ||||
| 
 | ||||
|     ui->rfOppBWText->setText(QString("%1k").arg((ui->rfOppBW->value()*m_rfSliderDivisor) / 1000.0, 0, 'f', 1)); | ||||
| 
 | ||||
|     ui->forceDecimator->setChecked(m_settings.m_forceDecimator); | ||||
|     ui->channelMute->setChecked(m_settings.m_channelMute); | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ | |||
| 
 | ||||
| const PluginDescriptor ATVModPlugin::m_pluginDescriptor = { | ||||
|     QString("ATV Modulator"), | ||||
|     QString("4.0.7"), | ||||
|     QString("4.3.2"), | ||||
|     QString("(c) Edouard Griffiths, F4EXB"), | ||||
|     QString("https://github.com/f4exb/sdrangel"), | ||||
|     true, | ||||
|  |  | |||
|  | @ -50,6 +50,11 @@ void ATVModSettings::resetToDefaults() | |||
|     m_overlayText = "ATV"; | ||||
|     m_rgbColor = QColor(255, 255, 255).rgb(); | ||||
|     m_title = "ATV Modulator"; | ||||
|     m_useReverseAPI = false; | ||||
|     m_reverseAPIAddress = "127.0.0.1"; | ||||
|     m_reverseAPIPort = 8888; | ||||
|     m_reverseAPIDeviceIndex = 0; | ||||
|     m_reverseAPIChannelIndex = 0; | ||||
| } | ||||
| 
 | ||||
| QByteArray ATVModSettings::serialize() const | ||||
|  | @ -76,6 +81,11 @@ QByteArray ATVModSettings::serialize() const | |||
|     } | ||||
| 
 | ||||
|     s.writeString(16, m_title); | ||||
|     s.writeBool(17, m_useReverseAPI); | ||||
|     s.writeString(18, m_reverseAPIAddress); | ||||
|     s.writeU32(19, m_reverseAPIPort); | ||||
|     s.writeU32(20, m_reverseAPIDeviceIndex); | ||||
|     s.writeU32(21, m_reverseAPIChannelIndex); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
|  | @ -94,6 +104,7 @@ bool ATVModSettings::deserialize(const QByteArray& data) | |||
|     { | ||||
|         QByteArray bytetmp; | ||||
|         qint32 tmp; | ||||
|         uint32_t utmp; | ||||
| 
 | ||||
|         d.readS32(1, &tmp, 0); | ||||
|         m_inputFrequencyOffset = tmp; | ||||
|  | @ -124,7 +135,20 @@ bool ATVModSettings::deserialize(const QByteArray& data) | |||
|         } | ||||
| 
 | ||||
|         d.readString(16, &m_title, "ATV Modulator"); | ||||
|         d.readBool(17, &m_useReverseAPI, false); | ||||
|         d.readString(18, &m_reverseAPIAddress, "127.0.0.1"); | ||||
|         d.readU32(19, &utmp, 0); | ||||
| 
 | ||||
|         if ((utmp > 1023) && (utmp < 65535)) { | ||||
|             m_reverseAPIPort = utmp; | ||||
|         } else { | ||||
|             m_reverseAPIPort = 8888; | ||||
|         } | ||||
| 
 | ||||
|         d.readU32(20, &utmp, 0); | ||||
|         m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; | ||||
|         d.readU32(21, &utmp, 0); | ||||
|         m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show More
		Ładowanie…
	
		Reference in New Issue
	
	 f4exb
						f4exb