mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-06-01 21:54:55 -04:00
Rename SDRDaemonSource device plugin to RemoteInput (1)
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
project(remoteinput)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
|
||||
if (HAS_SSSE3)
|
||||
message(STATUS "RemoteInput: use SSSE3 SIMD" )
|
||||
elseif (HAS_NEON)
|
||||
message(STATUS "RemoteInput: use Neon SIMD" )
|
||||
else()
|
||||
message(STATUS "RemoteInput: Unsupported architecture")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(remoteinput_SOURCES
|
||||
remoteinputbuffer.cpp
|
||||
remoteinputudphandler.cpp
|
||||
remoteinputgui.cpp
|
||||
remoteinput.cpp
|
||||
remoteinputsettings.cpp
|
||||
remoteinputplugin.cpp
|
||||
)
|
||||
|
||||
set(sremoteinput_HEADERS
|
||||
remoteinputbuffer.h
|
||||
remoteinputudphandler.h
|
||||
remoteinputgui.h
|
||||
remoteinput.h
|
||||
remoteinputsettings.h
|
||||
remoteinputplugin.h
|
||||
)
|
||||
|
||||
set(remoteinput_FORMS
|
||||
remoteinputgui.ui
|
||||
)
|
||||
|
||||
#include(${QT_USE_FILE})
|
||||
add_definitions(${QT_DEFINITIONS})
|
||||
add_definitions(-DQT_PLUGIN)
|
||||
add_definitions(-DQT_SHARED)
|
||||
|
||||
qt5_wrap_ui(remoteinput_FORMS_HEADERS ${remoteinput_FORMS})
|
||||
|
||||
add_library(inputremote SHARED
|
||||
${remoteinput_SOURCES}
|
||||
${remoteinput_HEADERS_MOC}
|
||||
${remoteinput_FORMS_HEADERS}
|
||||
)
|
||||
|
||||
if (BUILD_DEBIAN)
|
||||
target_include_directories(inputremote PUBLIC
|
||||
.
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
${LIBCM256CCSRC}
|
||||
)
|
||||
else (BUILD_DEBIAN)
|
||||
target_include_directories(inputremote PUBLIC
|
||||
.
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
${CM256CC_INCLUDE_DIR}
|
||||
)
|
||||
endif (BUILD_DEBIAN)
|
||||
|
||||
if (BUILD_DEBIAN)
|
||||
target_link_libraries(inputremote
|
||||
${QT_LIBRARIES}
|
||||
cm256cc
|
||||
sdrbase
|
||||
sdrgui
|
||||
swagger
|
||||
)
|
||||
else (BUILD_DEBIAN)
|
||||
target_link_libraries(inputremote
|
||||
${QT_LIBRARIES}
|
||||
${CM256CC_LIBRARIES}
|
||||
sdrbase
|
||||
sdrgui
|
||||
swagger
|
||||
)
|
||||
endif (BUILD_DEBIAN)
|
||||
|
||||
target_link_libraries(inputremote Qt5::Core Qt5::Widgets)
|
||||
|
||||
install(TARGETS inputremote DESTINATION lib/plugins/samplesource)
|
||||
@@ -0,0 +1,177 @@
|
||||
<h1>Remote input plugin</h1>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
This input sample source plugin gets its samples over tbe network from a SDRangel instance's Remote channel sink using UDP connection.
|
||||
|
||||
Forward Error Correction with a Cauchy MDS block erasure codec is used to prevent block loss. This can make the UDP transmission more robust particularly over WiFi links.
|
||||
|
||||
Please note that there is no provision for handling out of sync UDP blocks. It is assumed that frames and block numbers always increase with possible blocks missing. Such out of sync situation has never been encountered in practice.
|
||||
|
||||
The distant SDRangel instance that sends the data stream is controlled via its REST API using a separate control software for example [SDRangelcli](https://github.com/f4exb/sdrangelcli)
|
||||
|
||||
A sample size conversion takes place if the stream sample size sent by the distant instance and the Rx sample size of the local instance do not match (i.e. 16 to 24 bits or 24 to 16 bits). Best performace is obtained when both instances use the same sample size.
|
||||
|
||||
It is present only in Linux binary releases.
|
||||
|
||||
<h2>Build</h2>
|
||||
|
||||
The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_DIR=/opt/install/cm256cc` to the cmake commands.
|
||||
|
||||
<h2>Interface</h2>
|
||||
|
||||

|
||||
|
||||
<h3>1: Common stream parameters</h3>
|
||||
|
||||

|
||||
|
||||
<h4>1.1: Frequency</h4>
|
||||
|
||||
This is the center frequency in kHz sent in the meta data from the distant SDRangel instance and corresponds to the center frequency of reception.
|
||||
|
||||
<h4>1.2: Start/Stop</h4>
|
||||
|
||||
Device start / stop button.
|
||||
|
||||
- Blue triangle icon: device is ready and can be started
|
||||
- Green square icon: device is running and can be stopped
|
||||
|
||||
<h4>1.3: Record</h4>
|
||||
|
||||
Record I/Q stream toggle button
|
||||
|
||||
<h4>1.4: Stream sample rate</h4>
|
||||
|
||||
Stream I/Q sample rate in kS/s
|
||||
|
||||
<h3>2: Auto correction options and stream status</h3>
|
||||
|
||||

|
||||
|
||||
<h4>2.1: Auto correction options</h4>
|
||||
|
||||
These buttons control the local DSP auto correction options:
|
||||
|
||||
- **DC**: auto remove DC component
|
||||
- **IQ**: auto make I/Q balance
|
||||
|
||||
<h4>2.2: Receive buffer length</h4>
|
||||
|
||||
This is the main buffer (writes from UDP / reads from DSP engine) length in units of time (seconds). As read and write pointers are normally about half the buffer apart the nominal delay introduced by the buffer is the half of this value.
|
||||
|
||||
<h4>2.3: Main buffer R/W pointers positions</h4>
|
||||
|
||||
Read and write pointers should always be a half buffer distance buffer apart. This is the difference in percent of the main buffer size from this ideal position.
|
||||
|
||||
- When positive it means that the read pointer is leading
|
||||
- When negative it means that the write pointer is leading (read is lagging)
|
||||
|
||||
This corresponds to the value shown in the gauges above (9)
|
||||
|
||||
<h4>2.4: Date/time</h4>
|
||||
|
||||
This is the current timestamp of the block of data sent from the receiver. It is refreshed about every second. The plugin tries to take into account the buffer that is used between the data received from the network and the data effectively used by the system however this may not be extremely accurate. It is based on the timestamps sent from the Remote sink channel at the other hand that does not take into account its own buffers.
|
||||
|
||||
<h3>3: Main buffer R/W pointers gauge</h3>
|
||||
|
||||
There are two gauges separated by a dot in the center. Ideally these gauges should not display any value thus read and write pointers are always half a buffer apart. However due to the fact that a whole frame is reconstructed at once up to ~10% variation is normal and should appear on the left gauge (write leads).
|
||||
|
||||
- The left gauge is the negative gauge. It is the value in percent of buffer size from the write pointer position to the read pointer position when this difference is less than half of a buffer distance. It means that the writes are leading or reads are lagging.
|
||||
- The right gauge is the positive gauge. It is the value in percent of buffer size of the difference from the read pointer position to the write pointer position when this difference is less than half of a buffer distance. It menas that the writes are lagging or reads are leading.
|
||||
|
||||
The system tries to compensate read / write unbalance however at start or when a large stream disruption has occurred a delay of a few tens of seconds is necessary before read / write reaches equilibrium.
|
||||
|
||||
<h3>4: Data stream status</h3>
|
||||
|
||||

|
||||
|
||||
<h4>4.1: Sample size</h4>
|
||||
|
||||
This is the size in bits of a I or Q sample sent in the stream by the distant server.
|
||||
|
||||
<h4>4.2: Total number of frames and number of FEC blocks</h4>
|
||||
|
||||
This is the total number of frames and number of FEC blocks separated by a slash '/' as sent in the meta data block thus acknowledged by the distant server. When you set the number of FEC blocks with (4.1) the effect may not be immediate and this information can be used to monitor when it gets effectively set in the distant server.
|
||||
|
||||
A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction.
|
||||
|
||||
Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered.
|
||||
|
||||
<h4>4.3: Stream status</h4>
|
||||
|
||||
The color of the icon indicates stream status:
|
||||
|
||||
- Green: all original blocks have been received for all frames during the last polling timeframe (ex: 136)
|
||||
- No color: some original blocks were reconstructed from FEC blocks for some frames during the last polling timeframe (ex: between 128 and 135)
|
||||
- Red: some original blocks were definitely lost for some frames during the last polling timeframe (ex: less than 128)
|
||||
|
||||
<h4>4.4: Minimum total number of blocks per frame</h4>
|
||||
|
||||
This is the minimum total number of blocks per frame during the last polling period. If all blocks were received for all frames then this number is the nominal number of original blocks plus FEC blocks (Green lock icon). In our example this is 128+8 = 136.
|
||||
|
||||
If this number falls below 128 then some blocks are definitely lost and the lock lights in red.
|
||||
|
||||
<h4>4.5: Maximum number of FEC blocks used by frame</h4>
|
||||
|
||||
Maximum number of FEC blocks used for original blocks recovery during the last polling timeframe. Ideally this should be 0 when no blocks are lost but the system is able to correct lost blocks up to the nominal number of FEC blocks (Neutral lock icon).
|
||||
|
||||
<h4>4.6: Reset events counters</h4>
|
||||
|
||||
This push button can be used to reset the events counters (4.7 and 4.8) and reset the event counts timer (4.9)
|
||||
|
||||
<h4>4.7: Unrecoverable error events counter</h4>
|
||||
|
||||
This counter counts the unrecoverable error conditions found (i.e. 4.4 lower than 128) since the last counters reset.
|
||||
|
||||
<h4>4.8: Recoverable error events counter</h4>
|
||||
|
||||
This counter counts the unrecoverable error conditions found (i.e. 4.4 between 128 and 128 plus the number of FEC blocks) since the last counters reset.
|
||||
|
||||
<h4>4.9: events counters timer</h4>
|
||||
|
||||
This HH:mm:ss time display shows the time since the reset events counters button (4.6) was pushed.
|
||||
|
||||
<h3>5: Distant server API address and port</h3>
|
||||
|
||||

|
||||
|
||||
<h4>5.1: API connection indicator</h4>
|
||||
|
||||
The "API" label is lit in green when the connection is successful
|
||||
|
||||
<h4>5.2: API IP address</h4>
|
||||
|
||||
IP address of the distant SDRangel instance REST API
|
||||
|
||||
<h4>5.3: API port</h4>
|
||||
|
||||
Port of the distant SDRangel instance REST API
|
||||
|
||||
<h4>5.4: Validation button</h4>
|
||||
|
||||
When the return key is hit within the address (5.2) or port (5.3) the changes are effective immediately. You can also use this button to set again these values. Clicking on this button will send a request to the API to get the distant SDRangel instance information that is displayed in the API message box (8)
|
||||
|
||||
<h3>6: Local data address and port</h3>
|
||||
|
||||

|
||||
|
||||
<h4>6.1: Data IP address</h4>
|
||||
|
||||
IP address of the local network interface the distant SDRangel instance sends the data to
|
||||
|
||||
<h4>6.2: Data port</h4>
|
||||
|
||||
Local port the distant SDRangel instance sends the data to
|
||||
|
||||
<h4>6.3: Validation button</h4>
|
||||
|
||||
When the return key is hit within the address (5.2) or port (5.3) the changes are effective immediately. You can also use this button to set again these values.
|
||||
|
||||
<h3>7: Status message</h3>
|
||||
|
||||
The API status is displayed in this box. It shows "API OK" when the connection is successful and reply is OK
|
||||
|
||||
<h3>8: API information</h3>
|
||||
|
||||
This is the information returned by the API and is the distance SDRangel instance information if transaction is successful
|
||||
@@ -0,0 +1,514 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
|
||||
#include "SWGDeviceSettings.h"
|
||||
#include "SWGDeviceState.h"
|
||||
#include "SWGDeviceReport.h"
|
||||
#include "SWGSDRdaemonSourceReport.h"
|
||||
|
||||
#include "util/simpleserializer.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "device/devicesourceapi.h"
|
||||
#include "dsp/filerecord.h"
|
||||
|
||||
#include "remoteinput.h"
|
||||
#include "remoteinputudphandler.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(RemoteInput::MsgConfigureRemoteInput, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteInput::MsgConfigureRemoteInputTiming, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteInput::MsgReportRemoteInputAcquisition, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteInput::MsgReportRemoteInputStreamData, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteInput::MsgReportRemoteInputStreamTiming, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteInput::MsgFileRecord, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteInput::MsgStartStop, Message)
|
||||
|
||||
RemoteInput::RemoteInput(DeviceSourceAPI *deviceAPI) :
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_settings(),
|
||||
m_remoteInputUDPHandler(0),
|
||||
m_deviceDescription(),
|
||||
m_startingTimeStamp(0)
|
||||
{
|
||||
m_sampleFifo.setSize(96000 * 4);
|
||||
m_remoteInputUDPHandler = new RemoteInputUDPHandler(&m_sampleFifo, m_deviceAPI);
|
||||
|
||||
m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID()));
|
||||
m_deviceAPI->addSink(m_fileSink);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
}
|
||||
|
||||
RemoteInput::~RemoteInput()
|
||||
{
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
stop();
|
||||
m_deviceAPI->removeSink(m_fileSink);
|
||||
delete m_fileSink;
|
||||
delete m_remoteInputUDPHandler;
|
||||
}
|
||||
|
||||
void RemoteInput::destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void RemoteInput::init()
|
||||
{
|
||||
applySettings(m_settings, true);
|
||||
}
|
||||
|
||||
bool RemoteInput::start()
|
||||
{
|
||||
qDebug() << "RemoteInput::start";
|
||||
m_remoteInputUDPHandler->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoteInput::stop()
|
||||
{
|
||||
qDebug() << "RemoteInput::stop";
|
||||
m_remoteInputUDPHandler->stop();
|
||||
}
|
||||
|
||||
QByteArray RemoteInput::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool RemoteInput::deserialize(const QByteArray& data)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
if (!m_settings.deserialize(data))
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
success = false;
|
||||
}
|
||||
|
||||
MsgConfigureRemoteInput* message = MsgConfigureRemoteInput::create(m_settings, true);
|
||||
m_inputMessageQueue.push(message);
|
||||
|
||||
if (m_guiMessageQueue)
|
||||
{
|
||||
MsgConfigureRemoteInput* messageToGUI = MsgConfigureRemoteInput::create(m_settings, true);
|
||||
m_guiMessageQueue->push(messageToGUI);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void RemoteInput::setMessageQueueToGUI(MessageQueue *queue)
|
||||
{
|
||||
m_guiMessageQueue = queue;
|
||||
m_remoteInputUDPHandler->setMessageQueueToGUI(queue);
|
||||
}
|
||||
|
||||
const QString& RemoteInput::getDeviceDescription() const
|
||||
{
|
||||
return m_deviceDescription;
|
||||
}
|
||||
|
||||
int RemoteInput::getSampleRate() const
|
||||
{
|
||||
return m_remoteInputUDPHandler->getSampleRate();
|
||||
}
|
||||
|
||||
quint64 RemoteInput::getCenterFrequency() const
|
||||
{
|
||||
return m_remoteInputUDPHandler->getCenterFrequency();
|
||||
}
|
||||
|
||||
void RemoteInput::setCenterFrequency(qint64 centerFrequency)
|
||||
{
|
||||
(void) centerFrequency;
|
||||
}
|
||||
|
||||
std::time_t RemoteInput::getStartingTimeStamp() const
|
||||
{
|
||||
return m_startingTimeStamp;
|
||||
}
|
||||
|
||||
bool RemoteInput::isStreaming() const
|
||||
{
|
||||
return m_remoteInputUDPHandler->isStreaming();
|
||||
}
|
||||
|
||||
bool RemoteInput::handleMessage(const Message& message)
|
||||
{
|
||||
if (DSPSignalNotification::match(message))
|
||||
{
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) message;
|
||||
return m_fileSink->handleMessage(notif); // forward to file sink
|
||||
}
|
||||
else if (MsgFileRecord::match(message))
|
||||
{
|
||||
MsgFileRecord& conf = (MsgFileRecord&) message;
|
||||
qDebug() << "RemoteInput::handleMessage: MsgFileRecord: " << conf.getStartStop();
|
||||
|
||||
if (conf.getStartStop())
|
||||
{
|
||||
if (m_settings.m_fileRecordName.size() != 0) {
|
||||
m_fileSink->setFileName(m_settings.m_fileRecordName);
|
||||
} else {
|
||||
m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID());
|
||||
}
|
||||
|
||||
m_fileSink->startRecording();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fileSink->stopRecording();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgStartStop::match(message))
|
||||
{
|
||||
MsgStartStop& cmd = (MsgStartStop&) message;
|
||||
qDebug() << "RemoteInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop");
|
||||
|
||||
if (cmd.getStartStop())
|
||||
{
|
||||
if (m_deviceAPI->initAcquisition())
|
||||
{
|
||||
m_deviceAPI->startAcquisition();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_deviceAPI->stopAcquisition();
|
||||
}
|
||||
|
||||
if (m_settings.m_useReverseAPI) {
|
||||
webapiReverseSendStartStop(cmd.getStartStop());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureRemoteInput::match(message))
|
||||
{
|
||||
qDebug() << "RemoteInput::handleMessage:" << message.getIdentifier();
|
||||
MsgConfigureRemoteInput& conf = (MsgConfigureRemoteInput&) message;
|
||||
applySettings(conf.getSettings(), conf.getForce());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInput::applySettings(const RemoteInputSettings& settings, bool force)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
std::ostringstream os;
|
||||
QString remoteAddress;
|
||||
m_remoteInputUDPHandler->getRemoteAddress(remoteAddress);
|
||||
QList<QString> reverseAPIKeys;
|
||||
|
||||
if ((m_settings.m_dcBlock != settings.m_dcBlock) || force) {
|
||||
reverseAPIKeys.append("dcBlock");
|
||||
}
|
||||
if ((m_settings.m_iqCorrection != settings.m_iqCorrection) || force) {
|
||||
reverseAPIKeys.append("iqCorrection");
|
||||
}
|
||||
if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) {
|
||||
reverseAPIKeys.append("dataAddress");
|
||||
}
|
||||
if ((m_settings.m_dataPort != settings.m_dataPort) || force) {
|
||||
reverseAPIKeys.append("dataPort");
|
||||
}
|
||||
if ((m_settings.m_apiAddress != settings.m_apiAddress) || force) {
|
||||
reverseAPIKeys.append("apiAddress");
|
||||
}
|
||||
if ((m_settings.m_apiPort != settings.m_apiPort) || force) {
|
||||
reverseAPIKeys.append("apiPort");
|
||||
}
|
||||
if ((m_settings.m_fileRecordName != settings.m_fileRecordName) || force) {
|
||||
reverseAPIKeys.append("fileRecordName");
|
||||
}
|
||||
|
||||
if ((m_settings.m_dcBlock != settings.m_dcBlock) || (m_settings.m_iqCorrection != settings.m_iqCorrection) || force)
|
||||
{
|
||||
m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection);
|
||||
qDebug("RemoteInput::applySettings: corrections: DC block: %s IQ imbalance: %s",
|
||||
settings.m_dcBlock ? "true" : "false",
|
||||
settings.m_iqCorrection ? "true" : "false");
|
||||
}
|
||||
|
||||
m_remoteInputUDPHandler->configureUDPLink(settings.m_dataAddress, settings.m_dataPort);
|
||||
m_remoteInputUDPHandler->getRemoteAddress(remoteAddress);
|
||||
|
||||
mutexLocker.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);
|
||||
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
m_remoteAddress = remoteAddress;
|
||||
|
||||
qDebug() << "RemoteInput::applySettings: "
|
||||
<< " m_dataAddress: " << m_settings.m_dataAddress
|
||||
<< " m_dataPort: " << m_settings.m_dataPort
|
||||
<< " m_apiAddress: " << m_settings.m_apiAddress
|
||||
<< " m_apiPort: " << m_settings.m_apiPort
|
||||
<< " m_remoteAddress: " << m_remoteAddress;
|
||||
}
|
||||
|
||||
int RemoteInput::webapiRunGet(
|
||||
SWGSDRangel::SWGDeviceState& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
m_deviceAPI->getDeviceEngineStateStr(*response.getState());
|
||||
return 200;
|
||||
}
|
||||
|
||||
int RemoteInput::webapiRun(
|
||||
bool run,
|
||||
SWGSDRangel::SWGDeviceState& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
m_deviceAPI->getDeviceEngineStateStr(*response.getState());
|
||||
MsgStartStop *message = MsgStartStop::create(run);
|
||||
m_inputMessageQueue.push(message);
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
MsgStartStop *msgToGUI = MsgStartStop::create(run);
|
||||
m_guiMessageQueue->push(msgToGUI);
|
||||
}
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
int RemoteInput::webapiSettingsGet(
|
||||
SWGSDRangel::SWGDeviceSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setSdrDaemonSourceSettings(new SWGSDRangel::SWGSDRdaemonSourceSettings());
|
||||
response.getSdrDaemonSourceSettings()->init();
|
||||
webapiFormatDeviceSettings(response, m_settings);
|
||||
return 200;
|
||||
}
|
||||
|
||||
int RemoteInput::webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& deviceSettingsKeys,
|
||||
SWGSDRangel::SWGDeviceSettings& response, // query + response
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
RemoteInputSettings settings = m_settings;
|
||||
|
||||
if (deviceSettingsKeys.contains("apiAddress")) {
|
||||
settings.m_apiAddress = *response.getSdrDaemonSourceSettings()->getApiAddress();
|
||||
}
|
||||
if (deviceSettingsKeys.contains("apiPort")) {
|
||||
settings.m_apiPort = response.getSdrDaemonSourceSettings()->getApiPort();
|
||||
}
|
||||
if (deviceSettingsKeys.contains("dataAddress")) {
|
||||
settings.m_dataAddress = *response.getSdrDaemonSourceSettings()->getDataAddress();
|
||||
}
|
||||
if (deviceSettingsKeys.contains("dataPort")) {
|
||||
settings.m_dataPort = response.getSdrDaemonSourceSettings()->getDataPort();
|
||||
}
|
||||
if (deviceSettingsKeys.contains("dcBlock")) {
|
||||
settings.m_dcBlock = response.getSdrDaemonSourceSettings()->getDcBlock() != 0;
|
||||
}
|
||||
if (deviceSettingsKeys.contains("iqCorrection")) {
|
||||
settings.m_iqCorrection = response.getSdrDaemonSourceSettings()->getIqCorrection() != 0;
|
||||
}
|
||||
if (deviceSettingsKeys.contains("fileRecordName")) {
|
||||
settings.m_fileRecordName = *response.getSdrDaemonSourceSettings()->getFileRecordName();
|
||||
}
|
||||
if (deviceSettingsKeys.contains("useReverseAPI")) {
|
||||
settings.m_useReverseAPI = response.getSdrDaemonSourceSettings()->getUseReverseApi() != 0;
|
||||
}
|
||||
if (deviceSettingsKeys.contains("reverseAPIAddress")) {
|
||||
settings.m_reverseAPIAddress = *response.getSdrDaemonSourceSettings()->getReverseApiAddress();
|
||||
}
|
||||
if (deviceSettingsKeys.contains("reverseAPIPort")) {
|
||||
settings.m_reverseAPIPort = response.getSdrDaemonSourceSettings()->getReverseApiPort();
|
||||
}
|
||||
if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) {
|
||||
settings.m_reverseAPIDeviceIndex = response.getSdrDaemonSourceSettings()->getReverseApiDeviceIndex();
|
||||
}
|
||||
|
||||
MsgConfigureRemoteInput *msg = MsgConfigureRemoteInput::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
MsgConfigureRemoteInput *msgToGUI = MsgConfigureRemoteInput::create(settings, force);
|
||||
m_guiMessageQueue->push(msgToGUI);
|
||||
}
|
||||
|
||||
webapiFormatDeviceSettings(response, settings);
|
||||
return 200;
|
||||
}
|
||||
|
||||
void RemoteInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const RemoteInputSettings& settings)
|
||||
{
|
||||
response.getSdrDaemonSourceSettings()->setApiAddress(new QString(settings.m_apiAddress));
|
||||
response.getSdrDaemonSourceSettings()->setApiPort(settings.m_apiPort);
|
||||
response.getSdrDaemonSourceSettings()->setDataAddress(new QString(settings.m_dataAddress));
|
||||
response.getSdrDaemonSourceSettings()->setDataPort(settings.m_dataPort);
|
||||
response.getSdrDaemonSourceSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0);
|
||||
response.getSdrDaemonSourceSettings()->setIqCorrection(settings.m_iqCorrection);
|
||||
|
||||
if (response.getSdrDaemonSourceSettings()->getFileRecordName()) {
|
||||
*response.getSdrDaemonSourceSettings()->getFileRecordName() = settings.m_fileRecordName;
|
||||
} else {
|
||||
response.getSdrDaemonSourceSettings()->setFileRecordName(new QString(settings.m_fileRecordName));
|
||||
}
|
||||
|
||||
response.getSdrDaemonSourceSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
|
||||
|
||||
if (response.getSdrDaemonSourceSettings()->getReverseApiAddress()) {
|
||||
*response.getSdrDaemonSourceSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
|
||||
} else {
|
||||
response.getSdrDaemonSourceSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
|
||||
}
|
||||
|
||||
response.getSdrDaemonSourceSettings()->setReverseApiPort(settings.m_reverseAPIPort);
|
||||
response.getSdrDaemonSourceSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
|
||||
}
|
||||
|
||||
int RemoteInput::webapiReportGet(
|
||||
SWGSDRangel::SWGDeviceReport& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setSdrDaemonSourceReport(new SWGSDRangel::SWGSDRdaemonSourceReport());
|
||||
response.getSdrDaemonSourceReport()->init();
|
||||
webapiFormatDeviceReport(response);
|
||||
return 200;
|
||||
}
|
||||
|
||||
void RemoteInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response)
|
||||
{
|
||||
response.getSdrDaemonSourceReport()->setCenterFrequency(m_remoteInputUDPHandler->getCenterFrequency());
|
||||
response.getSdrDaemonSourceReport()->setSampleRate(m_remoteInputUDPHandler->getSampleRate());
|
||||
response.getSdrDaemonSourceReport()->setBufferRwBalance(m_remoteInputUDPHandler->getBufferGauge());
|
||||
|
||||
QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_remoteInputUDPHandler->getTVmSec());
|
||||
response.getSdrDaemonSourceReport()->setDaemonTimestamp(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz")));
|
||||
|
||||
response.getSdrDaemonSourceReport()->setMinNbBlocks(m_remoteInputUDPHandler->getMinNbBlocks());
|
||||
response.getSdrDaemonSourceReport()->setMaxNbRecovery(m_remoteInputUDPHandler->getMaxNbRecovery());
|
||||
}
|
||||
|
||||
void RemoteInput::webapiReverseSendSettings(QList<QString>& deviceSettingsKeys, const RemoteInputSettings& settings, bool force)
|
||||
{
|
||||
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
|
||||
swgDeviceSettings->setTx(0);
|
||||
swgDeviceSettings->setDeviceHwType(new QString("SDRdaemonSource"));
|
||||
swgDeviceSettings->setSdrDaemonSourceSettings(new SWGSDRangel::SWGSDRdaemonSourceSettings());
|
||||
SWGSDRangel::SWGSDRdaemonSourceSettings *swgSDRDaemonSourceSettings = swgDeviceSettings->getSdrDaemonSourceSettings();
|
||||
|
||||
// transfer data that has been modified. When force is on transfer all data except reverse API data
|
||||
|
||||
if (deviceSettingsKeys.contains("apiAddress") || force) {
|
||||
swgSDRDaemonSourceSettings->setApiAddress(new QString(settings.m_apiAddress));
|
||||
}
|
||||
if (deviceSettingsKeys.contains("apiPort") || force) {
|
||||
swgSDRDaemonSourceSettings->setApiPort(settings.m_apiPort);
|
||||
}
|
||||
if (deviceSettingsKeys.contains("dataAddress") || force) {
|
||||
swgSDRDaemonSourceSettings->setDataAddress(new QString(settings.m_dataAddress));
|
||||
}
|
||||
if (deviceSettingsKeys.contains("dataPort") || force) {
|
||||
swgSDRDaemonSourceSettings->setDataPort(settings.m_dataPort);
|
||||
}
|
||||
if (deviceSettingsKeys.contains("dcBlock") || force) {
|
||||
swgSDRDaemonSourceSettings->setDcBlock(settings.m_dcBlock ? 1 : 0);
|
||||
}
|
||||
if (deviceSettingsKeys.contains("iqCorrection") || force) {
|
||||
swgSDRDaemonSourceSettings->setIqCorrection(settings.m_iqCorrection ? 1 : 0);
|
||||
}
|
||||
if (deviceSettingsKeys.contains("fileRecordName") || force) {
|
||||
swgSDRDaemonSourceSettings->setFileRecordName(new QString(settings.m_fileRecordName));
|
||||
}
|
||||
|
||||
QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings")
|
||||
.arg(settings.m_reverseAPIAddress)
|
||||
.arg(settings.m_reverseAPIPort)
|
||||
.arg(settings.m_reverseAPIDeviceIndex);
|
||||
m_networkRequest.setUrl(QUrl(deviceSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgDeviceSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
|
||||
delete swgDeviceSettings;
|
||||
}
|
||||
|
||||
void RemoteInput::webapiReverseSendStartStop(bool start)
|
||||
{
|
||||
QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/run")
|
||||
.arg(m_settings.m_reverseAPIAddress)
|
||||
.arg(m_settings.m_reverseAPIPort)
|
||||
.arg(m_settings.m_reverseAPIDeviceIndex);
|
||||
m_networkRequest.setUrl(QUrl(deviceSettingsURL));
|
||||
|
||||
if (start) {
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "POST");
|
||||
} else {
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "DELETE");
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInput::networkManagerFinished(QNetworkReply *reply)
|
||||
{
|
||||
QNetworkReply::NetworkError replyError = reply->error();
|
||||
|
||||
if (replyError)
|
||||
{
|
||||
qWarning() << "RemoteInput::networkManagerFinished:"
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("SDRdaemonSourceInput::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_REMOTEINPUT_H
|
||||
#define INCLUDE_REMOTEINPUT_H
|
||||
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QTimer>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "dsp/devicesamplesource.h"
|
||||
|
||||
#include "remoteinputsettings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class DeviceSourceAPI;
|
||||
class RemoteInputUDPHandler;
|
||||
class FileRecord;
|
||||
|
||||
class RemoteInput : public DeviceSampleSource {
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureRemoteInput : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const RemoteInputSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureRemoteInput* create(const RemoteInputSettings& settings, bool force = false)
|
||||
{
|
||||
return new MsgConfigureRemoteInput(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
RemoteInputSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureRemoteInput(const RemoteInputSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureRemoteInputTiming : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
|
||||
static MsgConfigureRemoteInputTiming* create()
|
||||
{
|
||||
return new MsgConfigureRemoteInputTiming();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
MsgConfigureRemoteInputTiming() :
|
||||
Message()
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportRemoteInputAcquisition : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool getAcquisition() const { return m_acquisition; }
|
||||
|
||||
static MsgReportRemoteInputAcquisition* create(bool acquisition)
|
||||
{
|
||||
return new MsgReportRemoteInputAcquisition(acquisition);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_acquisition;
|
||||
|
||||
MsgReportRemoteInputAcquisition(bool acquisition) :
|
||||
Message(),
|
||||
m_acquisition(acquisition)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportRemoteInputStreamData : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
quint64 getCenterFrequency() const { return m_centerFrequency; }
|
||||
uint32_t get_tv_msec() const { return m_tv_msec; }
|
||||
|
||||
static MsgReportRemoteInputStreamData* create(int sampleRate, quint64 centerFrequency, uint64_t tv_msec)
|
||||
{
|
||||
return new MsgReportRemoteInputStreamData(sampleRate, centerFrequency, tv_msec);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_sampleRate;
|
||||
quint64 m_centerFrequency;
|
||||
uint64_t m_tv_msec;
|
||||
|
||||
MsgReportRemoteInputStreamData(int sampleRate, quint64 centerFrequency, uint64_t tv_msec) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_centerFrequency(centerFrequency),
|
||||
m_tv_msec(tv_msec)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportRemoteInputStreamTiming : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
uint64_t get_tv_msec() const { return m_tv_msec; }
|
||||
int getFramesDecodingStatus() const { return m_framesDecodingStatus; }
|
||||
bool allBlocksReceived() const { return m_allBlocksReceived; }
|
||||
float getBufferLengthInSecs() const { return m_bufferLenSec; }
|
||||
int32_t getBufferGauge() const { return m_bufferGauge; }
|
||||
int getMinNbBlocks() const { return m_minNbBlocks; }
|
||||
int getMinNbOriginalBlocks() const { return m_minNbOriginalBlocks; }
|
||||
int getMaxNbRecovery() const { return m_maxNbRecovery; }
|
||||
float getAvgNbBlocks() const { return m_avgNbBlocks; }
|
||||
float getAvgNbOriginalBlocks() const { return m_avgNbOriginalBlocks; }
|
||||
float getAvgNbRecovery() const { return m_avgNbRecovery; }
|
||||
int getNbOriginalBlocksPerFrame() const { return m_nbOriginalBlocksPerFrame; }
|
||||
int getNbFECBlocksPerFrame() const { return m_nbFECBlocksPerFrame; }
|
||||
int getSampleBits() const { return m_sampleBits; }
|
||||
int getSampleBytes() const { return m_sampleBytes; }
|
||||
|
||||
static MsgReportRemoteInputStreamTiming* create(uint64_t tv_msec,
|
||||
float bufferLenSec,
|
||||
int32_t bufferGauge,
|
||||
int framesDecodingStatus,
|
||||
bool allBlocksReceived,
|
||||
int minNbBlocks,
|
||||
int minNbOriginalBlocks,
|
||||
int maxNbRecovery,
|
||||
float avgNbBlocks,
|
||||
float avgNbOriginalBlocks,
|
||||
float avgNbRecovery,
|
||||
int nbOriginalBlocksPerFrame,
|
||||
int nbFECBlocksPerFrame,
|
||||
int sampleBits,
|
||||
int sampleBytes)
|
||||
{
|
||||
return new MsgReportRemoteInputStreamTiming(tv_msec,
|
||||
bufferLenSec,
|
||||
bufferGauge,
|
||||
framesDecodingStatus,
|
||||
allBlocksReceived,
|
||||
minNbBlocks,
|
||||
minNbOriginalBlocks,
|
||||
maxNbRecovery,
|
||||
avgNbBlocks,
|
||||
avgNbOriginalBlocks,
|
||||
avgNbRecovery,
|
||||
nbOriginalBlocksPerFrame,
|
||||
nbFECBlocksPerFrame,
|
||||
sampleBits,
|
||||
sampleBytes);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t m_tv_msec;
|
||||
int m_framesDecodingStatus;
|
||||
bool m_allBlocksReceived;
|
||||
float m_bufferLenSec;
|
||||
int32_t m_bufferGauge;
|
||||
int m_minNbBlocks;
|
||||
int m_minNbOriginalBlocks;
|
||||
int m_maxNbRecovery;
|
||||
float m_avgNbBlocks;
|
||||
float m_avgNbOriginalBlocks;
|
||||
float m_avgNbRecovery;
|
||||
int m_nbOriginalBlocksPerFrame;
|
||||
int m_nbFECBlocksPerFrame;
|
||||
int m_sampleBits;
|
||||
int m_sampleBytes;
|
||||
|
||||
MsgReportRemoteInputStreamTiming(uint64_t tv_msec,
|
||||
float bufferLenSec,
|
||||
int32_t bufferGauge,
|
||||
int framesDecodingStatus,
|
||||
bool allBlocksReceived,
|
||||
int minNbBlocks,
|
||||
int minNbOriginalBlocks,
|
||||
int maxNbRecovery,
|
||||
float avgNbBlocks,
|
||||
float avgNbOriginalBlocks,
|
||||
float avgNbRecovery,
|
||||
int nbOriginalBlocksPerFrame,
|
||||
int nbFECBlocksPerFrame,
|
||||
int sampleBits,
|
||||
int sampleBytes) :
|
||||
Message(),
|
||||
m_tv_msec(tv_msec),
|
||||
m_framesDecodingStatus(framesDecodingStatus),
|
||||
m_allBlocksReceived(allBlocksReceived),
|
||||
m_bufferLenSec(bufferLenSec),
|
||||
m_bufferGauge(bufferGauge),
|
||||
m_minNbBlocks(minNbBlocks),
|
||||
m_minNbOriginalBlocks(minNbOriginalBlocks),
|
||||
m_maxNbRecovery(maxNbRecovery),
|
||||
m_avgNbBlocks(avgNbBlocks),
|
||||
m_avgNbOriginalBlocks(avgNbOriginalBlocks),
|
||||
m_avgNbRecovery(avgNbRecovery),
|
||||
m_nbOriginalBlocksPerFrame(nbOriginalBlocksPerFrame),
|
||||
m_nbFECBlocksPerFrame(nbFECBlocksPerFrame),
|
||||
m_sampleBits(sampleBits),
|
||||
m_sampleBytes(sampleBytes)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgFileRecord : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool getStartStop() const { return m_startStop; }
|
||||
|
||||
static MsgFileRecord* create(bool startStop) {
|
||||
return new MsgFileRecord(startStop);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_startStop;
|
||||
|
||||
MsgFileRecord(bool startStop) :
|
||||
Message(),
|
||||
m_startStop(startStop)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgStartStop : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool getStartStop() const { return m_startStop; }
|
||||
|
||||
static MsgStartStop* create(bool startStop) {
|
||||
return new MsgStartStop(startStop);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_startStop;
|
||||
|
||||
MsgStartStop(bool startStop) :
|
||||
Message(),
|
||||
m_startStop(startStop)
|
||||
{ }
|
||||
};
|
||||
|
||||
RemoteInput(DeviceSourceAPI *deviceAPI);
|
||||
virtual ~RemoteInput();
|
||||
virtual void destroy();
|
||||
|
||||
virtual void init();
|
||||
virtual bool start();
|
||||
virtual void stop();
|
||||
|
||||
virtual QByteArray serialize() const;
|
||||
virtual bool deserialize(const QByteArray& data);
|
||||
|
||||
virtual void setMessageQueueToGUI(MessageQueue *queue);
|
||||
virtual const QString& getDeviceDescription() const;
|
||||
virtual int getSampleRate() const;
|
||||
virtual quint64 getCenterFrequency() const;
|
||||
virtual void setCenterFrequency(qint64 centerFrequency);
|
||||
std::time_t getStartingTimeStamp() const;
|
||||
bool isStreaming() const;
|
||||
|
||||
virtual bool handleMessage(const Message& message);
|
||||
|
||||
virtual int webapiSettingsGet(
|
||||
SWGSDRangel::SWGDeviceSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& deviceSettingsKeys,
|
||||
SWGSDRangel::SWGDeviceSettings& response, // query + response
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiReportGet(
|
||||
SWGSDRangel::SWGDeviceReport& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiRunGet(
|
||||
SWGSDRangel::SWGDeviceState& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiRun(
|
||||
bool run,
|
||||
SWGSDRangel::SWGDeviceState& response,
|
||||
QString& errorMessage);
|
||||
|
||||
private:
|
||||
DeviceSourceAPI *m_deviceAPI;
|
||||
QMutex m_mutex;
|
||||
RemoteInputSettings m_settings;
|
||||
RemoteInputUDPHandler* m_remoteInputUDPHandler;
|
||||
QString m_remoteAddress;
|
||||
QString m_deviceDescription;
|
||||
std::time_t m_startingTimeStamp;
|
||||
FileRecord *m_fileSink; //!< File sink to record device I/Q output
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
void applySettings(const RemoteInputSettings& settings, bool force = false);
|
||||
void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const RemoteInputSettings& settings);
|
||||
void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response);
|
||||
void webapiReverseSendSettings(QList<QString>& deviceSettingsKeys, const RemoteInputSettings& settings, bool force);
|
||||
void webapiReverseSendStartStop(bool start);
|
||||
|
||||
private slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
};
|
||||
|
||||
#endif // INCLUDE_REMOTEINPUT_H
|
||||
@@ -0,0 +1,70 @@
|
||||
#--------------------------------------------------------
|
||||
#
|
||||
# Pro file for Android and Windows builds with Qt Creator
|
||||
#
|
||||
#--------------------------------------------------------
|
||||
|
||||
TEMPLATE = lib
|
||||
CONFIG += plugin
|
||||
|
||||
QT += core gui widgets multimedia network opengl
|
||||
|
||||
TARGET = remoteinput
|
||||
|
||||
CONFIG(MINGW32):LIBCM256CCSRC = "C:\softs\cm256cc"
|
||||
CONFIG(MSVC):LIBCM256CCSRC = "C:\softs\cm256cc"
|
||||
CONFIG(macx):LIBCM256CCSRC = "../../../../deps/cm256cc"
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
INCLUDEPATH += ../../../exports
|
||||
INCLUDEPATH += ../../../sdrbase
|
||||
INCLUDEPATH += ../../../sdrgui
|
||||
INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client
|
||||
macx:INCLUDEPATH += /opt/local/include
|
||||
INCLUDEPATH += $$LIBCM256CCSRC
|
||||
|
||||
DEFINES += USE_SSE2=1
|
||||
QMAKE_CXXFLAGS += -msse2
|
||||
DEFINES += USE_SSSE3=1
|
||||
QMAKE_CXXFLAGS += -mssse3
|
||||
DEFINES += USE_SSE4_1=1
|
||||
QMAKE_CXXFLAGS += -msse4.1
|
||||
QMAKE_CXXFLAGS += -std=c++11
|
||||
|
||||
CONFIG(Release):build_subdir = release
|
||||
CONFIG(Debug):build_subdir = debug
|
||||
|
||||
CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0"
|
||||
CONFIG(MSVC):INCLUDEPATH += "C:\softs\boost_1_66_0"
|
||||
CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_69_0"
|
||||
|
||||
SOURCES += remoteinputbuffer.cpp\
|
||||
remoteinputgui.cpp\
|
||||
remoteinput.cpp\
|
||||
remoteinputsettings.cpp\
|
||||
remoteinputplugin.cpp\
|
||||
remoteinputeudphandler.cpp
|
||||
|
||||
HEADERS += remoteinputbuffer.h\
|
||||
remoteinputgui.h\
|
||||
remoteinput.h\
|
||||
remoteinputsettings.h\
|
||||
remoteinputplugin.h\
|
||||
remoteinputeudphandler.h
|
||||
|
||||
FORMS += remoteinputgui.ui
|
||||
|
||||
LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase
|
||||
LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui
|
||||
LIBS += -L../../../swagger/$${build_subdir} -lswagger
|
||||
LIBS += -L../../../cm256cc/$${build_subdir} -lcm256cc
|
||||
|
||||
macx {
|
||||
LIBS -= -L../../../nanomsg/$${build_subdir} -lnanomsg
|
||||
LIBS += -L/usr/local/lib -lnanomsg
|
||||
QMAKE_LFLAGS_SONAME = -Wl,-install_name,@rpath/
|
||||
}
|
||||
|
||||
RESOURCES = ../../../sdrgui/resources/res.qrc
|
||||
|
||||
CONFIG(MINGW32):DEFINES += USE_INTERNAL_TIMER=1
|
||||
@@ -0,0 +1,383 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <boost/crc.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include "remoteinputbuffer.h"
|
||||
|
||||
|
||||
|
||||
RemoteInputBuffer::RemoteInputBuffer() :
|
||||
m_decoderIndexHead(nbDecoderSlots/2),
|
||||
m_frameHead(0),
|
||||
m_curNbBlocks(0),
|
||||
m_minNbBlocks(256),
|
||||
m_curOriginalBlocks(0),
|
||||
m_minOriginalBlocks(128),
|
||||
m_curNbRecovery(0),
|
||||
m_maxNbRecovery(0),
|
||||
m_framesDecoded(true),
|
||||
m_readIndex(0),
|
||||
m_readBuffer(0),
|
||||
m_readSize(0),
|
||||
m_bufferLenSec(0.0f),
|
||||
m_nbReads(0),
|
||||
m_nbWrites(0),
|
||||
m_balCorrection(0),
|
||||
m_balCorrLimit(0)
|
||||
{
|
||||
m_currentMeta.init();
|
||||
m_framesNbBytes = nbDecoderSlots * sizeof(BufferFrame);
|
||||
m_wrDeltaEstimate = m_framesNbBytes / 2;
|
||||
m_tvOut_sec = 0;
|
||||
m_tvOut_usec = 0;
|
||||
m_readNbBytes = 1;
|
||||
m_paramsCM256.BlockBytes = sizeof(RemoteProtectedBlock); // never changes
|
||||
m_paramsCM256.OriginalCount = RemoteNbOrginalBlocks; // never changes
|
||||
|
||||
if (!m_cm256.isInitialized()) {
|
||||
m_cm256_OK = false;
|
||||
qDebug() << "RemoteInputBuffer::RemoteInputBuffer: cannot initialize CM256 library";
|
||||
} else {
|
||||
m_cm256_OK = true;
|
||||
}
|
||||
|
||||
std::fill(m_decoderSlots, m_decoderSlots + nbDecoderSlots, DecoderSlot());
|
||||
std::fill(m_frames, m_frames + nbDecoderSlots, BufferFrame());
|
||||
}
|
||||
|
||||
RemoteInputBuffer::~RemoteInputBuffer()
|
||||
{
|
||||
if (m_readBuffer) {
|
||||
delete[] m_readBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputBuffer::initDecodeAllSlots()
|
||||
{
|
||||
for (int i = 0; i < nbDecoderSlots; i++)
|
||||
{
|
||||
m_decoderSlots[i].m_blockCount = 0;
|
||||
m_decoderSlots[i].m_originalCount = 0;
|
||||
m_decoderSlots[i].m_recoveryCount = 0;
|
||||
m_decoderSlots[i].m_decoded = false;
|
||||
m_decoderSlots[i].m_metaRetrieved = false;
|
||||
resetOriginalBlocks(i);
|
||||
memset((void *) m_decoderSlots[i].m_recoveryBlocks, 0, RemoteNbOrginalBlocks * sizeof(RemoteProtectedBlock));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputBuffer::initDecodeSlot(int slotIndex)
|
||||
{
|
||||
// collect stats before voiding the slot
|
||||
|
||||
m_curNbBlocks = m_decoderSlots[slotIndex].m_blockCount;
|
||||
m_curOriginalBlocks = m_decoderSlots[slotIndex].m_originalCount;
|
||||
m_curNbRecovery = m_decoderSlots[slotIndex].m_recoveryCount;
|
||||
m_avgNbBlocks(m_curNbBlocks);
|
||||
m_avgOrigBlocks(m_curOriginalBlocks);
|
||||
m_avgNbRecovery(m_curNbRecovery);
|
||||
m_framesDecoded = m_framesDecoded && m_decoderSlots[slotIndex].m_decoded;
|
||||
|
||||
if (m_curNbBlocks < m_minNbBlocks) {
|
||||
m_minNbBlocks = m_curNbBlocks;
|
||||
}
|
||||
|
||||
if (m_curOriginalBlocks < m_minOriginalBlocks) {
|
||||
m_minOriginalBlocks = m_curOriginalBlocks;
|
||||
}
|
||||
|
||||
if (m_curNbRecovery > m_maxNbRecovery) {
|
||||
m_maxNbRecovery = m_curNbRecovery;
|
||||
}
|
||||
|
||||
// void the slot
|
||||
|
||||
m_decoderSlots[slotIndex].m_blockCount = 0;
|
||||
m_decoderSlots[slotIndex].m_originalCount = 0;
|
||||
m_decoderSlots[slotIndex].m_recoveryCount = 0;
|
||||
m_decoderSlots[slotIndex].m_decoded = false;
|
||||
m_decoderSlots[slotIndex].m_metaRetrieved = false;
|
||||
|
||||
resetOriginalBlocks(slotIndex);
|
||||
memset((void *) m_decoderSlots[slotIndex].m_recoveryBlocks, 0, RemoteNbOrginalBlocks * sizeof(RemoteProtectedBlock));
|
||||
}
|
||||
|
||||
void RemoteInputBuffer::initReadIndex()
|
||||
{
|
||||
m_readIndex = ((m_decoderIndexHead + (nbDecoderSlots/2)) % nbDecoderSlots) * sizeof(BufferFrame);
|
||||
m_wrDeltaEstimate = m_framesNbBytes / 2;
|
||||
m_nbReads = 0;
|
||||
m_nbWrites = 0;
|
||||
}
|
||||
|
||||
void RemoteInputBuffer::rwCorrectionEstimate(int slotIndex)
|
||||
{
|
||||
if (m_nbReads >= 40) // check every ~1s as tick is ~50ms
|
||||
{
|
||||
int targetPivotSlot = (slotIndex + (nbDecoderSlots/2)) % nbDecoderSlots; // slot at half buffer opposite of current write slot
|
||||
int targetPivotIndex = targetPivotSlot * sizeof(BufferFrame); // buffer index corresponding to start of above slot
|
||||
int normalizedReadIndex = (m_readIndex < targetPivotIndex ? m_readIndex + nbDecoderSlots * sizeof(BufferFrame) : m_readIndex)
|
||||
- (targetPivotSlot * sizeof(BufferFrame)); // normalize read index so it is positive and zero at start of pivot slot
|
||||
int dBytes;
|
||||
int rwDelta = (m_nbReads * m_readNbBytes) - (m_nbWrites * sizeof(BufferFrame));
|
||||
|
||||
if (normalizedReadIndex < (nbDecoderSlots/ 2) * (int) sizeof(BufferFrame)) // read leads
|
||||
{
|
||||
dBytes = - normalizedReadIndex - rwDelta;
|
||||
}
|
||||
else // read lags
|
||||
{
|
||||
dBytes = (nbDecoderSlots * sizeof(BufferFrame)) - normalizedReadIndex - rwDelta;
|
||||
}
|
||||
|
||||
m_balCorrection = (m_balCorrection / 4) + (dBytes / (int) (m_currentMeta.m_sampleBytes * 2 * m_nbReads)); // correction is in number of samples. Alpha = 0.25
|
||||
|
||||
if (m_balCorrection < -m_balCorrLimit) {
|
||||
m_balCorrection = -m_balCorrLimit;
|
||||
} else if (m_balCorrection > m_balCorrLimit) {
|
||||
m_balCorrection = m_balCorrLimit;
|
||||
}
|
||||
|
||||
m_nbReads = 0;
|
||||
m_nbWrites = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputBuffer::checkSlotData(int slotIndex)
|
||||
{
|
||||
int pseudoWriteIndex = slotIndex * sizeof(BufferFrame);
|
||||
m_wrDeltaEstimate = pseudoWriteIndex - m_readIndex;
|
||||
m_nbWrites++;
|
||||
|
||||
int rwDelayBytes = (m_wrDeltaEstimate > 0 ? m_wrDeltaEstimate : sizeof(BufferFrame) * nbDecoderSlots + m_wrDeltaEstimate);
|
||||
int sampleRate = m_currentMeta.m_sampleRate;
|
||||
|
||||
if (sampleRate > 0)
|
||||
{
|
||||
int64_t ts = m_currentMeta.m_tv_sec * 1000000LL + m_currentMeta.m_tv_usec;
|
||||
ts -= (rwDelayBytes * 1000000LL) / (sampleRate * 2 * m_currentMeta.m_sampleBytes);
|
||||
m_tvOut_sec = ts / 1000000LL;
|
||||
m_tvOut_usec = ts - (m_tvOut_sec * 1000000LL);
|
||||
}
|
||||
|
||||
if (!m_decoderSlots[slotIndex].m_decoded)
|
||||
{
|
||||
qDebug() << "RemoteInputBuffer::checkSlotData: incomplete frame:"
|
||||
<< " slotIndex: " << slotIndex
|
||||
<< " m_blockCount: " << m_decoderSlots[slotIndex].m_blockCount
|
||||
<< " m_recoveryCount: " << m_decoderSlots[slotIndex].m_recoveryCount;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputBuffer::writeData(char *array)
|
||||
{
|
||||
RemoteSuperBlock *superBlock = (RemoteSuperBlock *) array;
|
||||
int frameIndex = superBlock->m_header.m_frameIndex;
|
||||
int decoderIndex = frameIndex % nbDecoderSlots;
|
||||
|
||||
// frame break
|
||||
|
||||
if (m_frameHead == -1) // initial state
|
||||
{
|
||||
m_decoderIndexHead = decoderIndex; // new decoder slot head
|
||||
m_frameHead = frameIndex;
|
||||
initReadIndex(); // reset read index
|
||||
initDecodeAllSlots(); // initialize all slots
|
||||
}
|
||||
else if (m_frameHead != frameIndex) // frame break => new frame starts
|
||||
{
|
||||
m_decoderIndexHead = decoderIndex; // new decoder slot head
|
||||
m_frameHead = frameIndex; // new frame head
|
||||
checkSlotData(decoderIndex); // check slot before re-init
|
||||
rwCorrectionEstimate(decoderIndex);
|
||||
initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot
|
||||
}
|
||||
|
||||
// Block processing
|
||||
|
||||
if (m_decoderSlots[decoderIndex].m_blockCount < RemoteNbOrginalBlocks) // not enough blocks to decode -> store data
|
||||
{
|
||||
int blockIndex = superBlock->m_header.m_blockIndex;
|
||||
int blockCount = m_decoderSlots[decoderIndex].m_blockCount;
|
||||
int recoveryCount = m_decoderSlots[decoderIndex].m_recoveryCount;
|
||||
m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Index = blockIndex;
|
||||
|
||||
if (blockIndex == 0) // first block with meta
|
||||
{
|
||||
m_decoderSlots[decoderIndex].m_metaRetrieved = true;
|
||||
}
|
||||
|
||||
if (blockIndex < RemoteNbOrginalBlocks) // original data
|
||||
{
|
||||
m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) storeOriginalBlock(decoderIndex, blockIndex, superBlock->m_protectedBlock);
|
||||
m_decoderSlots[decoderIndex].m_originalCount++;
|
||||
}
|
||||
else // recovery data
|
||||
{
|
||||
m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount] = superBlock->m_protectedBlock;
|
||||
m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) &m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount];
|
||||
m_decoderSlots[decoderIndex].m_recoveryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
m_decoderSlots[decoderIndex].m_blockCount++;
|
||||
|
||||
if (m_decoderSlots[decoderIndex].m_blockCount == RemoteNbOrginalBlocks) // ready to decode
|
||||
{
|
||||
m_decoderSlots[decoderIndex].m_decoded = true;
|
||||
|
||||
if (m_cm256_OK && (m_decoderSlots[decoderIndex].m_recoveryCount > 0)) // recovery data used => need to decode FEC
|
||||
{
|
||||
m_paramsCM256.BlockBytes = sizeof(RemoteProtectedBlock); // never changes
|
||||
m_paramsCM256.OriginalCount = RemoteNbOrginalBlocks; // never changes
|
||||
|
||||
if (m_decoderSlots[decoderIndex].m_metaRetrieved) {
|
||||
m_paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks;
|
||||
} else {
|
||||
m_paramsCM256.RecoveryCount = m_decoderSlots[decoderIndex].m_recoveryCount;
|
||||
}
|
||||
|
||||
if (m_cm256.cm256_decode(m_paramsCM256, m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks)) // CM256 decode
|
||||
{
|
||||
qDebug() << "RemoteInputBuffer::writeData: decode CM256 error:"
|
||||
<< " decoderIndex: " << decoderIndex
|
||||
<< " m_blockCount: " << m_decoderSlots[decoderIndex].m_blockCount
|
||||
<< " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount
|
||||
<< " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "RemoteInputBuffer::writeData: decode CM256 success:"
|
||||
<< " decoderIndex: " << decoderIndex
|
||||
<< " m_blockCount: " << m_decoderSlots[decoderIndex].m_blockCount
|
||||
<< " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount
|
||||
<< " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount;
|
||||
|
||||
for (int ir = 0; ir < m_decoderSlots[decoderIndex].m_recoveryCount; ir++) // restore missing blocks
|
||||
{
|
||||
int recoveryIndex = RemoteNbOrginalBlocks - m_decoderSlots[decoderIndex].m_recoveryCount + ir;
|
||||
int blockIndex = m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Index;
|
||||
RemoteProtectedBlock *recoveredBlock = (RemoteProtectedBlock *) m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Block;
|
||||
|
||||
if (blockIndex == 0) // first block with meta
|
||||
{
|
||||
RemoteMetaDataFEC *metaData = (RemoteMetaDataFEC *) recoveredBlock;
|
||||
|
||||
boost::crc_32_type crc32;
|
||||
crc32.process_bytes(metaData, 20);
|
||||
|
||||
if (crc32.checksum() == metaData->m_crc32)
|
||||
{
|
||||
m_decoderSlots[decoderIndex].m_metaRetrieved = true;
|
||||
printMeta("RemoteInputBuffer::writeData: recovered meta", metaData);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "RemoteInputBuffer::writeData: recovered meta: invalid CRC32";
|
||||
}
|
||||
}
|
||||
|
||||
storeOriginalBlock(decoderIndex, blockIndex, *recoveredBlock);
|
||||
|
||||
qDebug() << "RemoteInputBuffer::writeData: recovered block #" << blockIndex;
|
||||
} // restore missing blocks
|
||||
} // CM256 decode
|
||||
} // recovery
|
||||
|
||||
if (m_decoderSlots[decoderIndex].m_metaRetrieved) // block zero with its meta data has been received
|
||||
{
|
||||
RemoteMetaDataFEC *metaData = getMetaData(decoderIndex);
|
||||
|
||||
if (!(*metaData == m_currentMeta))
|
||||
{
|
||||
uint32_t sampleRate = metaData->m_sampleRate;
|
||||
|
||||
if (sampleRate != 0)
|
||||
{
|
||||
m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * metaData->m_sampleBytes * 2);
|
||||
m_balCorrLimit = sampleRate / 1000; // +/- 1 ms correction max per read
|
||||
m_readNbBytes = (sampleRate * metaData->m_sampleBytes * 2) / 20;
|
||||
}
|
||||
|
||||
printMeta("RemoteInputBuffer::writeData: new meta", metaData); // print for change other than timestamp
|
||||
}
|
||||
|
||||
m_currentMeta = *metaData; // renew current meta
|
||||
} // check block 0
|
||||
} // decode
|
||||
}
|
||||
|
||||
uint8_t *RemoteInputBuffer::readData(int32_t length)
|
||||
{
|
||||
uint8_t *buffer = (uint8_t *) m_frames;
|
||||
uint32_t readIndex = m_readIndex;
|
||||
|
||||
m_nbReads++;
|
||||
|
||||
// SEGFAULT FIX: arbitratily truncate so that it does not exceed buffer length
|
||||
if (length > framesSize) {
|
||||
length = framesSize;
|
||||
}
|
||||
|
||||
if (m_readIndex + length < m_framesNbBytes) // ends before buffer bound
|
||||
{
|
||||
m_readIndex += length;
|
||||
return &buffer[readIndex];
|
||||
}
|
||||
else if (m_readIndex + length == m_framesNbBytes) // ends at buffer bound
|
||||
{
|
||||
m_readIndex = 0;
|
||||
return &buffer[readIndex];
|
||||
}
|
||||
else // ends after buffer bound
|
||||
{
|
||||
if (length > m_readSize) // reallocate composition buffer if necessary
|
||||
{
|
||||
if (m_readBuffer) {
|
||||
delete[] m_readBuffer;
|
||||
}
|
||||
|
||||
m_readBuffer = new uint8_t[length];
|
||||
m_readSize = length;
|
||||
}
|
||||
|
||||
std::memcpy((void *) m_readBuffer, (const void *) &buffer[m_readIndex], m_framesNbBytes - m_readIndex); // copy end of buffer
|
||||
length -= m_framesNbBytes - m_readIndex;
|
||||
std::memcpy((void *) &m_readBuffer[m_framesNbBytes - m_readIndex], (const void *) buffer, length); // copy start of buffer
|
||||
m_readIndex = length;
|
||||
return m_readBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputBuffer::printMeta(const QString& header, RemoteMetaDataFEC *metaData)
|
||||
{
|
||||
qDebug() << header << ": "
|
||||
<< "|" << metaData->m_centerFrequency
|
||||
<< ":" << metaData->m_sampleRate
|
||||
<< ":" << (int) (metaData->m_sampleBytes & 0xF)
|
||||
<< ":" << (int) metaData->m_sampleBits
|
||||
<< ":" << (int) metaData->m_nbOriginalBlocks
|
||||
<< ":" << (int) metaData->m_nbFECBlocks
|
||||
<< "|" << metaData->m_tv_sec
|
||||
<< ":" << metaData->m_tv_usec
|
||||
<< "|";
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTBUFFER_H_
|
||||
#define PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTBUFFER_H_
|
||||
|
||||
#include <channel/remotedatablock.h>
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
#include <cstdlib>
|
||||
#include "cm256.h"
|
||||
#include "util/movingaverage.h"
|
||||
|
||||
|
||||
#define REMOTEINPUT_UDPSIZE 512 // UDP payload size
|
||||
#define REMOTEINPUT_NBORIGINALBLOCKS 128 // number of sample blocks per frame excluding FEC blocks
|
||||
#define REMOTEINPUT_NBDECODERSLOTS 16 // power of two sub multiple of uint16_t size. A too large one is superfluous.
|
||||
|
||||
class RemoteInputBuffer
|
||||
{
|
||||
public:
|
||||
RemoteInputBuffer();
|
||||
~RemoteInputBuffer();
|
||||
|
||||
// R/W operations
|
||||
void writeData(char *array); //!< Write data into buffer.
|
||||
uint8_t *readData(int32_t length); //!< Read data from buffer
|
||||
|
||||
// meta data
|
||||
const RemoteMetaDataFEC& getCurrentMeta() const { return m_currentMeta; }
|
||||
|
||||
// samples timestamp
|
||||
uint32_t getTVOutSec() const { return m_tvOut_sec; }
|
||||
uint32_t getTVOutUsec() const { return m_tvOut_usec; }
|
||||
uint64_t getTVOutMSec() const { return (m_tvOut_sec * 1000LL) + (m_tvOut_usec/ 1000LL); }
|
||||
|
||||
// stats
|
||||
|
||||
int getCurNbBlocks() const { return m_curNbBlocks; }
|
||||
int getCurOriginalBlocks() const { return m_curOriginalBlocks; }
|
||||
int getCurNbRecovery() const { return m_curNbRecovery; }
|
||||
float getAvgNbBlocks() const { return m_avgNbBlocks; }
|
||||
float getAvgOriginalBlocks() const { return m_avgOrigBlocks; }
|
||||
float getAvgNbRecovery() const { return m_avgNbRecovery; }
|
||||
|
||||
int getMinNbBlocks()
|
||||
{
|
||||
int minNbBlocks = m_minNbBlocks;
|
||||
m_minNbBlocks = 256;
|
||||
return minNbBlocks;
|
||||
}
|
||||
|
||||
int getMinOriginalBlocks()
|
||||
{
|
||||
int minOriginalBlocks = m_minOriginalBlocks;
|
||||
m_minOriginalBlocks = 128;
|
||||
return minOriginalBlocks;
|
||||
}
|
||||
|
||||
int getMaxNbRecovery()
|
||||
{
|
||||
int maxNbRecovery = m_maxNbRecovery;
|
||||
m_maxNbRecovery = 0;
|
||||
return maxNbRecovery;
|
||||
}
|
||||
|
||||
bool allFramesDecoded()
|
||||
{
|
||||
bool framesDecoded = m_framesDecoded;
|
||||
m_framesDecoded = true;
|
||||
return framesDecoded;
|
||||
}
|
||||
|
||||
float getBufferLengthInSecs() const { return m_bufferLenSec; }
|
||||
int32_t getRWBalanceCorrection() const { return m_balCorrection; }
|
||||
|
||||
/** Get buffer gauge value in % of buffer size ([-50:50])
|
||||
* [-50:0] : write leads or read lags
|
||||
* [0:50] : read leads or write lags
|
||||
*/
|
||||
inline int32_t getBufferGauge() const
|
||||
{
|
||||
if (m_framesNbBytes)
|
||||
{
|
||||
int32_t val = (m_wrDeltaEstimate * 100) / (int32_t) m_framesNbBytes;
|
||||
int32_t ret = val < 0 ? -val - 50 : 50 -val;
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0; // default position
|
||||
}
|
||||
}
|
||||
|
||||
static const int framesSize = REMOTEINPUT_NBDECODERSLOTS * (RemoteNbOrginalBlocks - 1) * RemoteNbBytesPerBlock;
|
||||
|
||||
private:
|
||||
static const int nbDecoderSlots = REMOTEINPUT_NBDECODERSLOTS;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct BufferFrame
|
||||
{
|
||||
RemoteProtectedBlock m_blocks[RemoteNbOrginalBlocks - 1];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct DecoderSlot
|
||||
{
|
||||
RemoteProtectedBlock m_blockZero; //!< First block of a frame. Has meta data.
|
||||
RemoteProtectedBlock m_originalBlocks[RemoteNbOrginalBlocks]; //!< Original blocks retrieved directly or by later FEC
|
||||
RemoteProtectedBlock m_recoveryBlocks[RemoteNbOrginalBlocks]; //!< Recovery blocks (FEC blocks) with max size
|
||||
CM256::cm256_block m_cm256DescriptorBlocks[RemoteNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes)
|
||||
int m_blockCount; //!< number of blocks received for this frame
|
||||
int m_originalCount; //!< number of original blocks received
|
||||
int m_recoveryCount; //!< number of recovery blocks received
|
||||
bool m_decoded; //!< true if decoded
|
||||
bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved
|
||||
};
|
||||
|
||||
RemoteMetaDataFEC m_currentMeta; //!< Stored current meta data
|
||||
CM256::cm256_encoder_params m_paramsCM256; //!< CM256 decoder parameters block
|
||||
DecoderSlot m_decoderSlots[nbDecoderSlots]; //!< CM256 decoding control/buffer slots
|
||||
BufferFrame m_frames[nbDecoderSlots]; //!< Samples buffer
|
||||
int m_framesNbBytes; //!< Number of bytes in samples buffer
|
||||
int m_decoderIndexHead; //!< index of the current head frame slot in decoding slots
|
||||
int m_frameHead; //!< index of the current head frame sent
|
||||
int m_curNbBlocks; //!< (stats) instantaneous number of blocks received
|
||||
int m_minNbBlocks; //!< (stats) minimum number of blocks received since last poll
|
||||
int m_curOriginalBlocks; //!< (stats) instantanous number of original blocks received
|
||||
int m_minOriginalBlocks; //!< (stats) minimum number of original blocks received since last poll
|
||||
int m_curNbRecovery; //!< (stats) instantaneous number of recovery blocks used
|
||||
int m_maxNbRecovery; //!< (stats) maximum number of recovery blocks used since last poll
|
||||
MovingAverageUtil<int, int, 10> m_avgNbBlocks; //!< (stats) average number of blocks received
|
||||
MovingAverageUtil<int, int, 10> m_avgOrigBlocks; //!< (stats) average number of original blocks received
|
||||
MovingAverageUtil<int, int, 10> m_avgNbRecovery; //!< (stats) average number of recovery blocks used
|
||||
bool m_framesDecoded; //!< [stats] true if all frames were decoded since last poll
|
||||
int m_readIndex; //!< current byte read index in frames buffer
|
||||
int m_wrDeltaEstimate; //!< Sampled estimate of write to read indexes difference
|
||||
uint32_t m_tvOut_sec; //!< Estimated returned samples timestamp (seconds)
|
||||
uint32_t m_tvOut_usec; //!< Estimated returned samples timestamp (microseconds)
|
||||
int m_readNbBytes; //!< Nominal number of bytes per read (50ms)
|
||||
|
||||
uint8_t* m_readBuffer; //!< Read buffer to hold samples when looping back to beginning of raw buffer
|
||||
int m_readSize; //!< Read buffer size
|
||||
|
||||
float m_bufferLenSec;
|
||||
|
||||
int m_nbReads; //!< Number of buffer reads since start of auto R/W balance correction period
|
||||
int m_nbWrites; //!< Number of buffer writes since start of auto R/W balance correction period
|
||||
int m_balCorrection; //!< R/W balance correction in number of samples
|
||||
int m_balCorrLimit; //!< Correction absolute value limit in number of samples
|
||||
CM256 m_cm256; //!< CM256 library
|
||||
bool m_cm256_OK; //!< CM256 library initialized OK
|
||||
|
||||
inline RemoteProtectedBlock* storeOriginalBlock(int slotIndex, int blockIndex, const RemoteProtectedBlock& protectedBlock)
|
||||
{
|
||||
if (blockIndex == 0) {
|
||||
// m_decoderSlots[slotIndex].m_originalBlocks[0] = protectedBlock;
|
||||
// return &m_decoderSlots[slotIndex].m_originalBlocks[0];
|
||||
m_decoderSlots[slotIndex].m_blockZero = protectedBlock;
|
||||
return &m_decoderSlots[slotIndex].m_blockZero;
|
||||
} else {
|
||||
// m_decoderSlots[slotIndex].m_originalBlocks[blockIndex] = protectedBlock;
|
||||
// return &m_decoderSlots[slotIndex].m_originalBlocks[blockIndex];
|
||||
m_frames[slotIndex].m_blocks[blockIndex - 1] = protectedBlock;
|
||||
return &m_frames[slotIndex].m_blocks[blockIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
inline RemoteProtectedBlock& getOriginalBlock(int slotIndex, int blockIndex)
|
||||
{
|
||||
if (blockIndex == 0) {
|
||||
// return m_decoderSlots[slotIndex].m_originalBlocks[0];
|
||||
return m_decoderSlots[slotIndex].m_blockZero;
|
||||
} else {
|
||||
// return m_decoderSlots[slotIndex].m_originalBlocks[blockIndex];
|
||||
return m_frames[slotIndex].m_blocks[blockIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
inline RemoteMetaDataFEC *getMetaData(int slotIndex)
|
||||
{
|
||||
// return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_originalBlocks[0];
|
||||
return (RemoteMetaDataFEC *) &m_decoderSlots[slotIndex].m_blockZero;
|
||||
}
|
||||
|
||||
inline void resetOriginalBlocks(int slotIndex)
|
||||
{
|
||||
// memset((void *) m_decoderSlots[slotIndex].m_originalBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock));
|
||||
memset((void *) &m_decoderSlots[slotIndex].m_blockZero, 0, sizeof(RemoteProtectedBlock));
|
||||
memset((void *) m_frames[slotIndex].m_blocks, 0, (RemoteNbOrginalBlocks - 1) * sizeof(RemoteProtectedBlock));
|
||||
}
|
||||
|
||||
void initDecodeAllSlots();
|
||||
void initReadIndex();
|
||||
void rwCorrectionEstimate(int slotIndex);
|
||||
void checkSlotData(int slotIndex);
|
||||
void initDecodeSlot(int slotIndex);
|
||||
|
||||
static void printMeta(const QString& header, RemoteMetaDataFEC *metaData);
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTBUFFER_H_ */
|
||||
@@ -0,0 +1,657 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "ui_remoteinputgui.h"
|
||||
#include "gui/colormapper.h"
|
||||
#include "gui/glspectrum.h"
|
||||
#include "gui/crightclickenabler.h"
|
||||
#include "gui/basicdevicesettingsdialog.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "mainwindow.h"
|
||||
#include "util/simpleserializer.h"
|
||||
#include "device/devicesourceapi.h"
|
||||
#include "device/deviceuiset.h"
|
||||
#include "remoteinputgui.h"
|
||||
|
||||
|
||||
RemoteInputGui::RemoteInputGui(DeviceUISet *deviceUISet, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::RemoteInputGui),
|
||||
m_deviceUISet(deviceUISet),
|
||||
m_settings(),
|
||||
m_sampleSource(0),
|
||||
m_acquisition(false),
|
||||
m_streamSampleRate(0),
|
||||
m_streamCenterFrequency(0),
|
||||
m_lastEngineState(DSPDeviceSourceEngine::StNotStarted),
|
||||
m_framesDecodingStatus(0),
|
||||
m_bufferLengthInSecs(0.0),
|
||||
m_bufferGauge(-50),
|
||||
m_nbOriginalBlocks(128),
|
||||
m_nbFECBlocks(0),
|
||||
m_sampleBits(16), // assume 16 bits to start with
|
||||
m_sampleBytes(2),
|
||||
m_samplesCount(0),
|
||||
m_tickCount(0),
|
||||
m_addressEdited(false),
|
||||
m_dataPortEdited(false),
|
||||
m_countUnrecoverable(0),
|
||||
m_countRecovered(0),
|
||||
m_doApplySettings(true),
|
||||
m_forceSettings(true),
|
||||
m_txDelay(0.0)
|
||||
{
|
||||
m_paletteGreenText.setColor(QPalette::WindowText, Qt::green);
|
||||
m_paletteWhiteText.setColor(QPalette::WindowText, Qt::white);
|
||||
|
||||
m_startingTimeStampms = 0;
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
||||
ui->centerFrequency->setValueRange(7, 0, 9999999U);
|
||||
|
||||
CRightClickEnabler *startStopRightClickEnabler = new CRightClickEnabler(ui->startStop);
|
||||
connect(startStopRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &)));
|
||||
|
||||
displaySettings();
|
||||
|
||||
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
|
||||
m_statusTimer.start(500);
|
||||
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
|
||||
|
||||
m_sampleSource = (RemoteInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource();
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection);
|
||||
m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
|
||||
m_eventsTime.start();
|
||||
displayEventCounts();
|
||||
displayEventTimer();
|
||||
|
||||
m_forceSettings = true;
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
RemoteInputGui::~RemoteInputGui()
|
||||
{
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void RemoteInputGui::blockApplySettings(bool block)
|
||||
{
|
||||
m_doApplySettings = !block;
|
||||
}
|
||||
|
||||
void RemoteInputGui::destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void RemoteInputGui::setName(const QString& name)
|
||||
{
|
||||
setObjectName(name);
|
||||
}
|
||||
|
||||
QString RemoteInputGui::getName() const
|
||||
{
|
||||
return objectName();
|
||||
}
|
||||
|
||||
void RemoteInputGui::resetToDefaults()
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
displaySettings();
|
||||
m_forceSettings = true;
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
QByteArray RemoteInputGui::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool RemoteInputGui::deserialize(const QByteArray& data)
|
||||
{
|
||||
qDebug("RemoteInputGui::deserialize");
|
||||
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
displaySettings();
|
||||
m_forceSettings = true;
|
||||
sendSettings();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qint64 RemoteInputGui::getCenterFrequency() const
|
||||
{
|
||||
return m_streamCenterFrequency;
|
||||
}
|
||||
|
||||
void RemoteInputGui::setCenterFrequency(qint64 centerFrequency)
|
||||
{
|
||||
(void) centerFrequency;
|
||||
}
|
||||
|
||||
bool RemoteInputGui::handleMessage(const Message& message)
|
||||
{
|
||||
if (RemoteInput::MsgConfigureRemoteInput::match(message))
|
||||
{
|
||||
const RemoteInput::MsgConfigureRemoteInput& cfg = (RemoteInput::MsgConfigureRemoteInput&) message;
|
||||
m_settings = cfg.getSettings();
|
||||
blockApplySettings(true);
|
||||
displaySettings();
|
||||
blockApplySettings(false);
|
||||
return true;
|
||||
}
|
||||
else if (RemoteInput::MsgReportRemoteInputAcquisition::match(message))
|
||||
{
|
||||
m_acquisition = ((RemoteInput::MsgReportRemoteInputAcquisition&)message).getAcquisition();
|
||||
updateWithAcquisition();
|
||||
return true;
|
||||
}
|
||||
else if (RemoteInput::MsgReportRemoteInputStreamData::match(message))
|
||||
{
|
||||
m_startingTimeStampms = ((RemoteInput::MsgReportRemoteInputStreamData&)message).get_tv_msec();
|
||||
|
||||
qDebug() << "RemoteInputGui::handleMessage: RemoteInput::MsgReportRemoteInputStreamData: "
|
||||
<< " : " << m_startingTimeStampms << " ms";
|
||||
|
||||
updateWithStreamTime();
|
||||
return true;
|
||||
}
|
||||
else if (RemoteInput::MsgReportRemoteInputStreamTiming::match(message))
|
||||
{
|
||||
m_startingTimeStampms = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).get_tv_msec();
|
||||
m_framesDecodingStatus = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getFramesDecodingStatus();
|
||||
m_allBlocksReceived = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).allBlocksReceived();
|
||||
m_bufferLengthInSecs = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getBufferLengthInSecs();
|
||||
m_bufferGauge = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getBufferGauge();
|
||||
m_minNbBlocks = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getMinNbBlocks();
|
||||
m_minNbOriginalBlocks = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getMinNbOriginalBlocks();
|
||||
m_maxNbRecovery = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getMaxNbRecovery();
|
||||
m_avgNbBlocks = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getAvgNbBlocks();
|
||||
m_avgNbOriginalBlocks = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getAvgNbOriginalBlocks();
|
||||
m_avgNbRecovery = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getAvgNbRecovery();
|
||||
m_nbOriginalBlocks = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getNbOriginalBlocksPerFrame();
|
||||
m_sampleBits = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getSampleBits();
|
||||
m_sampleBytes = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getSampleBytes();
|
||||
|
||||
int nbFECBlocks = ((RemoteInput::MsgReportRemoteInputStreamTiming&)message).getNbFECBlocksPerFrame();
|
||||
|
||||
if (m_nbFECBlocks != nbFECBlocks) {
|
||||
m_nbFECBlocks = nbFECBlocks;
|
||||
}
|
||||
|
||||
updateWithStreamTime();
|
||||
return true;
|
||||
}
|
||||
else if (RemoteInput::MsgStartStop::match(message))
|
||||
{
|
||||
RemoteInput::MsgStartStop& notif = (RemoteInput::MsgStartStop&) message;
|
||||
blockApplySettings(true);
|
||||
ui->startStop->setChecked(notif.getStartStop());
|
||||
blockApplySettings(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != 0)
|
||||
{
|
||||
//qDebug("RemoteInputGui::handleInputMessages: message: %s", message->getIdentifier());
|
||||
|
||||
if (DSPSignalNotification::match(*message))
|
||||
{
|
||||
DSPSignalNotification* notif = (DSPSignalNotification*) message;
|
||||
|
||||
if (notif->getSampleRate() != m_streamSampleRate) {
|
||||
m_streamSampleRate = notif->getSampleRate();
|
||||
}
|
||||
|
||||
m_streamCenterFrequency = notif->getCenterFrequency();
|
||||
|
||||
qDebug("RemoteInputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency());
|
||||
|
||||
updateSampleRateAndFrequency();
|
||||
DSPSignalNotification *fwd = new DSPSignalNotification(*notif);
|
||||
m_sampleSource->getInputMessageQueue()->push(fwd);
|
||||
|
||||
delete message;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (handleMessage(*message))
|
||||
{
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::updateSampleRateAndFrequency()
|
||||
{
|
||||
m_deviceUISet->getSpectrum()->setSampleRate(m_streamSampleRate);
|
||||
m_deviceUISet->getSpectrum()->setCenterFrequency(m_streamCenterFrequency);
|
||||
ui->deviceRateText->setText(tr("%1k").arg((float)m_streamSampleRate / 1000));
|
||||
blockApplySettings(true);
|
||||
ui->centerFrequency->setValue(m_streamCenterFrequency / 1000);
|
||||
blockApplySettings(false);
|
||||
}
|
||||
|
||||
void RemoteInputGui::displaySettings()
|
||||
{
|
||||
blockApplySettings(true);
|
||||
|
||||
ui->centerFrequency->setValue(m_streamCenterFrequency / 1000);
|
||||
ui->deviceRateText->setText(tr("%1k").arg(m_streamSampleRate / 1000.0));
|
||||
|
||||
ui->apiAddress->setText(m_settings.m_apiAddress);
|
||||
ui->apiPort->setText(tr("%1").arg(m_settings.m_apiPort));
|
||||
ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
|
||||
ui->dataAddress->setText(m_settings.m_dataAddress);
|
||||
|
||||
ui->dcOffset->setChecked(m_settings.m_dcBlock);
|
||||
ui->iqImbalance->setChecked(m_settings.m_iqCorrection);
|
||||
|
||||
blockApplySettings(false);
|
||||
}
|
||||
|
||||
void RemoteInputGui::sendSettings()
|
||||
{
|
||||
if(!m_updateTimer.isActive())
|
||||
m_updateTimer.start(100);
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_apiApplyButton_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
m_settings.m_apiAddress = ui->apiAddress->text();
|
||||
|
||||
bool ctlOk;
|
||||
int udpApiPort = ui->apiPort->text().toInt(&ctlOk);
|
||||
|
||||
if((ctlOk) && (udpApiPort >= 1024) && (udpApiPort < 65535)) {
|
||||
m_settings.m_apiPort = udpApiPort;
|
||||
}
|
||||
|
||||
sendSettings();
|
||||
|
||||
QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort);
|
||||
m_networkRequest.setUrl(QUrl(infoURL));
|
||||
m_networkManager->get(m_networkRequest);
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_dataApplyButton_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
m_settings.m_dataAddress = ui->dataAddress->text();
|
||||
|
||||
bool dataOk;
|
||||
int udpDataPort = ui->dataPort->text().toInt(&dataOk);
|
||||
|
||||
if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) {
|
||||
m_settings.m_dataPort = udpDataPort;
|
||||
}
|
||||
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_apiAddress_returnPressed()
|
||||
{
|
||||
m_settings.m_apiAddress = ui->apiAddress->text();
|
||||
|
||||
QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort);
|
||||
m_networkRequest.setUrl(QUrl(infoURL));
|
||||
m_networkManager->get(m_networkRequest);
|
||||
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_dataAddress_returnPressed()
|
||||
{
|
||||
m_settings.m_dataAddress = ui->dataAddress->text();
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_dataPort_returnPressed()
|
||||
{
|
||||
bool dataOk;
|
||||
int udpDataPort = ui->dataPort->text().toInt(&dataOk);
|
||||
|
||||
if((!dataOk) || (udpDataPort < 1024) || (udpDataPort > 65535))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings.m_dataPort = udpDataPort;
|
||||
sendSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_apiPort_returnPressed()
|
||||
{
|
||||
bool ctlOk;
|
||||
int udpApiPort = ui->apiPort->text().toInt(&ctlOk);
|
||||
|
||||
if((!ctlOk) || (udpApiPort < 1024) || (udpApiPort > 65535))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings.m_apiPort = udpApiPort;
|
||||
|
||||
QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort);
|
||||
m_networkRequest.setUrl(QUrl(infoURL));
|
||||
m_networkManager->get(m_networkRequest);
|
||||
|
||||
sendSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_dcOffset_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_dcBlock = checked;
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_iqImbalance_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_iqCorrection = checked;
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_startStop_toggled(bool checked)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
RemoteInput::MsgStartStop *message = RemoteInput::MsgStartStop::create(checked);
|
||||
m_sampleSource->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_record_toggled(bool checked)
|
||||
{
|
||||
if (checked) {
|
||||
ui->record->setStyleSheet("QToolButton { background-color : red; }");
|
||||
} else {
|
||||
ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
|
||||
}
|
||||
|
||||
RemoteInput::MsgFileRecord* message = RemoteInput::MsgFileRecord::create(checked);
|
||||
m_sampleSource->getInputMessageQueue()->push(message);
|
||||
}
|
||||
|
||||
void RemoteInputGui::on_eventCountsReset_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
m_countUnrecoverable = 0;
|
||||
m_countRecovered = 0;
|
||||
m_eventsTime.start();
|
||||
displayEventCounts();
|
||||
displayEventTimer();
|
||||
}
|
||||
|
||||
void RemoteInputGui::displayEventCounts()
|
||||
{
|
||||
QString nstr = QString("%1").arg(m_countUnrecoverable, 3, 10, QChar('0'));
|
||||
ui->eventUnrecText->setText(nstr);
|
||||
nstr = QString("%1").arg(m_countRecovered, 3, 10, QChar('0'));
|
||||
ui->eventRecText->setText(nstr);
|
||||
}
|
||||
|
||||
void RemoteInputGui::displayEventTimer()
|
||||
{
|
||||
int elapsedTimeMillis = m_eventsTime.elapsed();
|
||||
QTime recordLength(0, 0, 0, 0);
|
||||
recordLength = recordLength.addSecs(elapsedTimeMillis/1000);
|
||||
QString s_time = recordLength.toString("HH:mm:ss");
|
||||
ui->eventCountsTimeText->setText(s_time);
|
||||
}
|
||||
|
||||
void RemoteInputGui::updateWithAcquisition()
|
||||
{
|
||||
}
|
||||
|
||||
void RemoteInputGui::updateWithStreamTime()
|
||||
{
|
||||
bool updateEventCounts = false;
|
||||
QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_startingTimeStampms);
|
||||
QString s_date = dt.toString("yyyy-MM-dd HH:mm:ss.zzz");
|
||||
ui->absTimeText->setText(s_date);
|
||||
|
||||
if (m_framesDecodingStatus == 2)
|
||||
{
|
||||
ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }");
|
||||
}
|
||||
else if (m_framesDecodingStatus == 1)
|
||||
{
|
||||
if (m_countRecovered < 999) m_countRecovered++;
|
||||
updateEventCounts = true;
|
||||
ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(56,56,56); }");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_countUnrecoverable < 999) m_countUnrecoverable++;
|
||||
updateEventCounts = true;
|
||||
ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }");
|
||||
}
|
||||
|
||||
QString s = QString::number(m_bufferLengthInSecs, 'f', 1);
|
||||
ui->bufferLenSecsText->setText(tr("%1").arg(s));
|
||||
|
||||
s = QString::number(m_bufferGauge, 'f', 0);
|
||||
ui->bufferRWBalanceText->setText(tr("%1").arg(s));
|
||||
|
||||
ui->bufferGaugeNegative->setValue((m_bufferGauge < 0 ? -m_bufferGauge : 0));
|
||||
ui->bufferGaugePositive->setValue((m_bufferGauge < 0 ? 0 : m_bufferGauge));
|
||||
|
||||
s = QString::number(m_minNbBlocks, 'f', 0);
|
||||
ui->minNbBlocksText->setText(tr("%1").arg(s));
|
||||
|
||||
s = QString("%1").arg(m_maxNbRecovery, 2, 10, QChar('0'));
|
||||
ui->maxNbRecoveryText->setText(tr("%1").arg(s));
|
||||
|
||||
s = QString::number(m_nbOriginalBlocks + m_nbFECBlocks, 'f', 0);
|
||||
QString s1 = QString("%1").arg(m_nbFECBlocks, 2, 10, QChar('0'));
|
||||
ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1));
|
||||
|
||||
ui->sampleBitsText->setText(tr("%1b").arg(m_sampleBits));
|
||||
|
||||
if (updateEventCounts)
|
||||
{
|
||||
displayEventCounts();
|
||||
}
|
||||
|
||||
displayEventTimer();
|
||||
}
|
||||
|
||||
void RemoteInputGui::updateHardware()
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
qDebug() << "RemoteInputGui::updateHardware";
|
||||
RemoteInput::MsgConfigureRemoteInput* message =
|
||||
RemoteInput::MsgConfigureRemoteInput::create(m_settings, m_forceSettings);
|
||||
m_sampleSource->getInputMessageQueue()->push(message);
|
||||
m_forceSettings = false;
|
||||
m_updateTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::updateStatus()
|
||||
{
|
||||
if (m_sampleSource->isStreaming())
|
||||
{
|
||||
int state = m_deviceUISet->m_deviceSourceAPI->state();
|
||||
|
||||
if (m_lastEngineState != state)
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case DSPDeviceSourceEngine::StNotStarted:
|
||||
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
|
||||
break;
|
||||
case DSPDeviceSourceEngine::StIdle:
|
||||
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
|
||||
break;
|
||||
case DSPDeviceSourceEngine::StRunning:
|
||||
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
|
||||
break;
|
||||
case DSPDeviceSourceEngine::StError:
|
||||
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
|
||||
QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSourceAPI->errorMessage());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_lastEngineState = state;
|
||||
}
|
||||
|
||||
ui->startStop->setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
|
||||
ui->startStop->setChecked(false);
|
||||
ui->startStop->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::networkManagerFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error())
|
||||
{
|
||||
ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
|
||||
ui->statusText->setText(reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
|
||||
try
|
||||
{
|
||||
QByteArray jsonBytes(answer.toStdString().c_str());
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error);
|
||||
|
||||
if (error.error == QJsonParseError::NoError)
|
||||
{
|
||||
ui->apiAddressLabel->setStyleSheet("QLabel { background-color : green; }");
|
||||
ui->statusText->setText(QString("API OK"));
|
||||
analyzeApiReply(doc.object());
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
|
||||
QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset);
|
||||
ui->statusText->setText(QString("JSON error. See log"));
|
||||
qInfo().noquote() << "RemoteInputGui::networkManagerFinished" << errorMsg;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
|
||||
QString errorMsg = QString("Error parsing request: ") + ex.what();
|
||||
ui->statusText->setText("Error parsing request. See log for details");
|
||||
qInfo().noquote() << "RemoteInputGui::networkManagerFinished" << errorMsg;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::analyzeApiReply(const QJsonObject& jsonObject)
|
||||
{
|
||||
QString infoLine;
|
||||
|
||||
if (jsonObject.contains("version")) {
|
||||
infoLine = "v" + jsonObject["version"].toString();
|
||||
}
|
||||
|
||||
if (jsonObject.contains("qtVersion")) {
|
||||
infoLine += " Qt" + jsonObject["qtVersion"].toString();
|
||||
}
|
||||
|
||||
if (jsonObject.contains("architecture")) {
|
||||
infoLine += " " + jsonObject["architecture"].toString();
|
||||
}
|
||||
|
||||
if (jsonObject.contains("os")) {
|
||||
infoLine += " " + jsonObject["os"].toString();
|
||||
}
|
||||
|
||||
if (jsonObject.contains("dspRxBits") && jsonObject.contains("dspTxBits")) {
|
||||
infoLine += QString(" %1/%2b").arg(jsonObject["dspRxBits"].toInt()).arg(jsonObject["dspTxBits"].toInt());
|
||||
}
|
||||
|
||||
if (infoLine.size() > 0) {
|
||||
ui->infoText->setText(infoLine);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
{
|
||||
BasicDeviceSettingsDialog dialog(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.move(p);
|
||||
dialog.exec();
|
||||
|
||||
m_settings.m_useReverseAPI = dialog.useReverseAPI();
|
||||
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
||||
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
||||
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
|
||||
|
||||
sendSettings();
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_REMOTEINPUTGUI_H
|
||||
#define INCLUDE_REMOTEINPUTGUI_H
|
||||
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "plugin/plugininstancegui.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "remoteinput.h"
|
||||
|
||||
class DeviceUISet;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QJsonObject;
|
||||
|
||||
namespace Ui {
|
||||
class RemoteInputGui;
|
||||
}
|
||||
|
||||
class RemoteInputGui : public QWidget, public PluginInstanceGUI {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RemoteInputGui(DeviceUISet *deviceUISet, QWidget* parent = 0);
|
||||
virtual ~RemoteInputGui();
|
||||
virtual void destroy();
|
||||
|
||||
void setName(const QString& name);
|
||||
QString getName() const;
|
||||
|
||||
void resetToDefaults();
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
virtual qint64 getCenterFrequency() const;
|
||||
virtual void setCenterFrequency(qint64 centerFrequency);
|
||||
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
|
||||
virtual bool handleMessage(const Message& message);
|
||||
|
||||
private:
|
||||
Ui::RemoteInputGui* ui;
|
||||
|
||||
DeviceUISet* m_deviceUISet;
|
||||
RemoteInputSettings m_settings; //!< current settings
|
||||
RemoteInput* m_sampleSource;
|
||||
bool m_acquisition;
|
||||
int m_streamSampleRate; //!< Sample rate of received stream
|
||||
quint64 m_streamCenterFrequency; //!< Center frequency of received stream
|
||||
QTimer m_updateTimer;
|
||||
QTimer m_statusTimer;
|
||||
int m_lastEngineState;
|
||||
MessageQueue m_inputMessageQueue;
|
||||
|
||||
// int m_sampleRate;
|
||||
// quint64 m_centerFrequency;
|
||||
uint64_t m_startingTimeStampms;
|
||||
int m_framesDecodingStatus;
|
||||
bool m_allBlocksReceived;
|
||||
float m_bufferLengthInSecs;
|
||||
int32_t m_bufferGauge;
|
||||
int m_minNbBlocks;
|
||||
int m_minNbOriginalBlocks;
|
||||
int m_maxNbRecovery;
|
||||
float m_avgNbBlocks;
|
||||
float m_avgNbOriginalBlocks;
|
||||
float m_avgNbRecovery;
|
||||
int m_nbOriginalBlocks;
|
||||
int m_nbFECBlocks;
|
||||
int m_sampleBits;
|
||||
int m_sampleBytes;
|
||||
|
||||
int m_samplesCount;
|
||||
std::size_t m_tickCount;
|
||||
|
||||
bool m_addressEdited;
|
||||
bool m_dataPortEdited;
|
||||
|
||||
uint32_t m_countUnrecoverable;
|
||||
uint32_t m_countRecovered;
|
||||
QTime m_eventsTime;
|
||||
|
||||
bool m_doApplySettings;
|
||||
bool m_forceSettings;
|
||||
double m_txDelay;
|
||||
|
||||
QPalette m_paletteGreenText;
|
||||
QPalette m_paletteWhiteText;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
void blockApplySettings(bool block);
|
||||
void displaySettings();
|
||||
void displayTime();
|
||||
void sendSettings();
|
||||
void updateWithAcquisition();
|
||||
void updateWithStreamTime();
|
||||
void updateSampleRateAndFrequency();
|
||||
void displayEventCounts();
|
||||
void displayEventTimer();
|
||||
void analyzeApiReply(const QJsonObject& jsonObject);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void on_apiApplyButton_clicked(bool checked);
|
||||
void on_dataApplyButton_clicked(bool checked);
|
||||
void on_dcOffset_toggled(bool checked);
|
||||
void on_iqImbalance_toggled(bool checked);
|
||||
void on_apiAddress_returnPressed();
|
||||
void on_apiPort_returnPressed();
|
||||
void on_dataAddress_returnPressed();
|
||||
void on_dataPort_returnPressed();
|
||||
void on_startStop_toggled(bool checked);
|
||||
void on_record_toggled(bool checked);
|
||||
void on_eventCountsReset_clicked(bool checked);
|
||||
void updateHardware();
|
||||
void updateStatus();
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
void openDeviceSettingsDialog(const QPoint& p);
|
||||
};
|
||||
|
||||
#endif // INCLUDE_REMOTEINPUTGUI_H
|
||||
@@ -0,0 +1,830 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RemoteInputGui</class>
|
||||
<widget class="QWidget" name="RemoteInputGui">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>270</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>360</width>
|
||||
<height>270</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Sans</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Remote Input</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_freq">
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="deviceUILayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="deviceButtonsLayout">
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="startStop">
|
||||
<property name="toolTip">
|
||||
<string>start/stop acquisition</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/play.png</normaloff>
|
||||
<normalon>:/stop.png</normalon>:/play.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="record">
|
||||
<property name="toolTip">
|
||||
<string>Toggle record I/Q samples from device</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/record_off.png</normaloff>:/record_off.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="deviceRateLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="deviceRateText">
|
||||
<property name="toolTip">
|
||||
<string>I/Q sample rate kS/s</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>00000k</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="freqLeftSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ValueDial" name="centerFrequency" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Mono</family>
|
||||
<pointsize>20</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remote center frequency in kHz</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="freqUnits">
|
||||
<property name="text">
|
||||
<string> kHz</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="freqRightlSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_address">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="gridLayout_corr">
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="dcOffset">
|
||||
<property name="toolTip">
|
||||
<string>DC Offset auto correction</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>DC</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="iqImbalance">
|
||||
<property name="toolTip">
|
||||
<string>IQ Imbalance auto correction</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>IQ</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="bufferLenSecsText">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>28</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Buffer length in seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>00.0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="lineStream7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="bufferRWBalanceText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Main buffer read/write positions unbalance (%): positive means read leads</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-00</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="lineStream2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="absTimeText">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Stream timestamp</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2015-01-01 00:00:00.000</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="bufferGaugesLayout">
|
||||
<item>
|
||||
<widget class="QProgressBar" name="bufferGaugeNegative">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Main buffer read/write positions unbalance: write leads read lags</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="invertedAppearance">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="format">
|
||||
<string>%v</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="bufferGaugeCenterDot">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>2</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="bufferGaugePositive">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Main buffer read/write positions unbalance: read leads write lags</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_freq_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="streamLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="sampleBitsText">
|
||||
<property name="toolTip">
|
||||
<string>Sample size (bits)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>16b</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="nominalNbBlocksText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Nominal number of blocks per frame: Total/FEC</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>000/00</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="allFramesDecoded">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Frames status: green = all original received, none = some recovered by FEC, red = some lost</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/locked.png</normaloff>:/locked.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="minNbBlocksText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Minimum number of blocks retrieved per frame</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>000</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="maxNbRecoveryText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Maximum number of recovery blocks used per frame</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>00</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="eventCountsReset">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Event counts reset</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="eventUnrecText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Number of unrecoverable errors since event counts reset</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>000</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="eventRecText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Number of correctable errors since event counts reset</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>000</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="eventCountsTimeText">
|
||||
<property name="toolTip">
|
||||
<string>Time since last event counts reset</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>00:00:00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_rateTime">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="apiAddressLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="apiAddressLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>API</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="apiAddress">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remote API IPv4 address</string>
|
||||
</property>
|
||||
<property name="inputMask">
|
||||
<string>000.000.000.000</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0.0.0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="apiPortSeparator">
|
||||
<property name="text">
|
||||
<string>:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="apiPort">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remote API port</string>
|
||||
</property>
|
||||
<property name="inputMask">
|
||||
<string>00000</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="apiApplyButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="dataAddressLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="dataAddressLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="dataAddress">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Local data connection IPv4 address</string>
|
||||
</property>
|
||||
<property name="inputMask">
|
||||
<string>000.000.000.000</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0.0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="dataPortSeparator">
|
||||
<property name="text">
|
||||
<string>:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="dataPort">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Local data connection port</string>
|
||||
</property>
|
||||
<property name="inputMask">
|
||||
<string>00000</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="dataApplyButton">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="statusLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="statusText">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="infoLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="infoText">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="padLayout">
|
||||
<item>
|
||||
<spacer name="verticalPadSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ValueDial</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/valuedial.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,113 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QtPlugin>
|
||||
|
||||
#include "plugin/pluginapi.h"
|
||||
#include "util/simpleserializer.h"
|
||||
#include "device/devicesourceapi.h"
|
||||
|
||||
#ifdef SERVER_MODE
|
||||
#include "remoteinput.h"
|
||||
#else
|
||||
#include "remoteinputgui.h"
|
||||
#endif
|
||||
#include "remoteinputplugin.h"
|
||||
|
||||
const PluginDescriptor RemoteInputPlugin::m_pluginDescriptor = {
|
||||
QString("Remote input"),
|
||||
QString("4.4.3"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
QString("https://github.com/f4exb/sdrangel")
|
||||
};
|
||||
|
||||
const QString RemoteInputPlugin::m_hardwareID = "RemoteInput";
|
||||
const QString RemoteInputPlugin::m_deviceTypeID = REMOTEINPUT_DEVICE_TYPE_ID;
|
||||
|
||||
RemoteInputPlugin::RemoteInputPlugin(QObject* parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
const PluginDescriptor& RemoteInputPlugin::getPluginDescriptor() const
|
||||
{
|
||||
return m_pluginDescriptor;
|
||||
}
|
||||
|
||||
void RemoteInputPlugin::initPlugin(PluginAPI* pluginAPI)
|
||||
{
|
||||
pluginAPI->registerSampleSource(m_deviceTypeID, this);
|
||||
}
|
||||
|
||||
PluginInterface::SamplingDevices RemoteInputPlugin::enumSampleSources()
|
||||
{
|
||||
SamplingDevices result;
|
||||
|
||||
result.append(SamplingDevice(
|
||||
"RemoteInput",
|
||||
m_hardwareID,
|
||||
m_deviceTypeID,
|
||||
QString::null,
|
||||
0,
|
||||
PluginInterface::SamplingDevice::BuiltInDevice,
|
||||
true,
|
||||
1,
|
||||
0));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef SERVER_MODE
|
||||
PluginInstanceGUI* RemoteInputPlugin::createSampleSourcePluginInstanceGUI(
|
||||
const QString& sourceId __attribute((unused)),
|
||||
QWidget **widget __attribute((unused)),
|
||||
DeviceUISet *deviceUISet __attribute((unused)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
PluginInstanceGUI* RemoteInputPlugin::createSampleSourcePluginInstanceGUI(
|
||||
const QString& sourceId,
|
||||
QWidget **widget,
|
||||
DeviceUISet *deviceUISet)
|
||||
{
|
||||
if(sourceId == m_deviceTypeID)
|
||||
{
|
||||
RemoteInputGui* gui = new RemoteInputGui(deviceUISet);
|
||||
*widget = gui;
|
||||
return gui;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
DeviceSampleSource *RemoteInputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI)
|
||||
{
|
||||
if (sourceId == m_deviceTypeID)
|
||||
{
|
||||
RemoteInput* input = new RemoteInput(deviceAPI);
|
||||
return input;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_REMOTEINPUTPLUGIN_H
|
||||
#define INCLUDE_REMOTEINPUTPLUGIN_H
|
||||
|
||||
#include <QObject>
|
||||
#include "plugin/plugininterface.h"
|
||||
|
||||
#define REMOTEINPUT_DEVICE_TYPE_ID "sdrangel.samplesource.remoteinput"
|
||||
|
||||
class PluginAPI;
|
||||
|
||||
class RemoteInputPlugin : public QObject, public PluginInterface {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(PluginInterface)
|
||||
Q_PLUGIN_METADATA(IID REMOTEINPUT_DEVICE_TYPE_ID)
|
||||
|
||||
public:
|
||||
explicit RemoteInputPlugin(QObject* parent = NULL);
|
||||
|
||||
const PluginDescriptor& getPluginDescriptor() const;
|
||||
void initPlugin(PluginAPI* pluginAPI);
|
||||
|
||||
virtual SamplingDevices enumSampleSources();
|
||||
virtual PluginInstanceGUI* createSampleSourcePluginInstanceGUI(
|
||||
const QString& sourceId,
|
||||
QWidget **widget,
|
||||
DeviceUISet *deviceUISet);
|
||||
virtual DeviceSampleSource* createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI);
|
||||
|
||||
static const QString m_hardwareID;
|
||||
static const QString m_deviceTypeID;
|
||||
|
||||
private:
|
||||
static const PluginDescriptor m_pluginDescriptor;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_REMOTEINPUTPLUGIN_H
|
||||
@@ -0,0 +1,102 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2017 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "util/simpleserializer.h"
|
||||
#include "remoteinputsettings.h"
|
||||
|
||||
RemoteInputSettings::RemoteInputSettings()
|
||||
{
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
void RemoteInputSettings::resetToDefaults()
|
||||
{
|
||||
m_apiAddress = "127.0.0.1";
|
||||
m_apiPort = 9091;
|
||||
m_dataAddress = "127.0.0.1";
|
||||
m_dataPort = 9090;
|
||||
m_dcBlock = false;
|
||||
m_iqCorrection = false;
|
||||
m_fileRecordName = "";
|
||||
m_useReverseAPI = false;
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
m_reverseAPIDeviceIndex = 0;
|
||||
}
|
||||
|
||||
QByteArray RemoteInputSettings::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
|
||||
s.writeString(5, m_apiAddress);
|
||||
s.writeU32(6, m_apiPort);
|
||||
s.writeU32(7, m_dataPort);
|
||||
s.writeString(8, m_dataAddress);
|
||||
s.writeBool(9, m_dcBlock);
|
||||
s.writeBool(10, m_iqCorrection);
|
||||
s.writeBool(11, m_useReverseAPI);
|
||||
s.writeString(12, m_reverseAPIAddress);
|
||||
s.writeU32(13, m_reverseAPIPort);
|
||||
s.writeU32(14, m_reverseAPIDeviceIndex);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool RemoteInputSettings::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if (!d.isValid())
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d.getVersion() == 1)
|
||||
{
|
||||
quint32 uintval;
|
||||
|
||||
d.readString(5, &m_apiAddress, "127.0.0.1");
|
||||
d.readU32(6, &uintval, 9090);
|
||||
m_apiPort = uintval % (1<<16);
|
||||
d.readU32(7, &uintval, 9091);
|
||||
m_dataPort = uintval % (1<<16);
|
||||
d.readString(8, &m_dataAddress, "127.0.0.1");
|
||||
d.readBool(9, &m_dcBlock, false);
|
||||
d.readBool(10, &m_iqCorrection, false);
|
||||
d.readBool(11, &m_useReverseAPI, false);
|
||||
d.readString(12, &m_reverseAPIAddress, "127.0.0.1");
|
||||
d.readU32(13, &uintval, 0);
|
||||
|
||||
if ((uintval > 1023) && (uintval < 65535)) {
|
||||
m_reverseAPIPort = uintval;
|
||||
} else {
|
||||
m_reverseAPIPort = 8888;
|
||||
}
|
||||
|
||||
d.readU32(14, &uintval, 0);
|
||||
m_reverseAPIDeviceIndex = uintval > 99 ? 99 : uintval;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2017 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTSETTINGS_H_
|
||||
#define PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTSETTINGS_H_
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
struct RemoteInputSettings {
|
||||
QString m_apiAddress;
|
||||
quint16 m_apiPort;
|
||||
QString m_dataAddress;
|
||||
quint16 m_dataPort;
|
||||
bool m_dcBlock;
|
||||
bool m_iqCorrection;
|
||||
QString m_fileRecordName;
|
||||
bool m_useReverseAPI;
|
||||
QString m_reverseAPIAddress;
|
||||
uint16_t m_reverseAPIPort;
|
||||
uint16_t m_reverseAPIDeviceIndex;
|
||||
|
||||
RemoteInputSettings();
|
||||
void resetToDefaults();
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
};
|
||||
|
||||
#endif /* PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTSETTINGS_H_ */
|
||||
@@ -0,0 +1,362 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QUdpSocket>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include <device/devicesourceapi.h>
|
||||
|
||||
#include "remoteinputudphandler.h"
|
||||
#include "remoteinput.h"
|
||||
|
||||
RemoteInputUDPHandler::RemoteInputUDPHandler(SampleSinkFifo *sampleFifo, DeviceSourceAPI *deviceAPI) :
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_masterTimer(deviceAPI->getMasterTimer()),
|
||||
m_masterTimerConnected(false),
|
||||
m_running(false),
|
||||
m_rateDivider(1000/REMOTEINPUT_THROTTLE_MS),
|
||||
m_dataSocket(0),
|
||||
m_dataAddress(QHostAddress::LocalHost),
|
||||
m_remoteAddress(QHostAddress::LocalHost),
|
||||
m_dataPort(9090),
|
||||
m_dataConnected(false),
|
||||
m_udpBuf(0),
|
||||
m_udpReadBytes(0),
|
||||
m_sampleFifo(sampleFifo),
|
||||
m_samplerate(0),
|
||||
m_centerFrequency(0),
|
||||
m_tv_msec(0),
|
||||
m_outputMessageQueueToGUI(0),
|
||||
m_tickCount(0),
|
||||
m_samplesCount(0),
|
||||
m_timer(0),
|
||||
m_throttlems(REMOTEINPUT_THROTTLE_MS),
|
||||
m_readLengthSamples(0),
|
||||
m_readLength(0),
|
||||
m_converterBuffer(0),
|
||||
m_converterBufferNbSamples(0),
|
||||
m_throttleToggle(false),
|
||||
m_autoCorrBuffer(true)
|
||||
{
|
||||
m_udpBuf = new char[RemoteUdpSize];
|
||||
|
||||
#ifdef USE_INTERNAL_TIMER
|
||||
#warning "Uses internal timer"
|
||||
m_timer = new QTimer();
|
||||
m_timer->start(50);
|
||||
m_throttlems = m_timer->interval();
|
||||
#else
|
||||
m_throttlems = m_masterTimer.interval();
|
||||
#endif
|
||||
m_rateDivider = 1000 / m_throttlems;
|
||||
}
|
||||
|
||||
RemoteInputUDPHandler::~RemoteInputUDPHandler()
|
||||
{
|
||||
stop();
|
||||
delete[] m_udpBuf;
|
||||
if (m_converterBuffer) { delete[] m_converterBuffer; }
|
||||
#ifdef USE_INTERNAL_TIMER
|
||||
if (m_timer) {
|
||||
delete m_timer;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoteInputUDPHandler::start()
|
||||
{
|
||||
qDebug("RemoteInputUDPHandler::start");
|
||||
|
||||
if (m_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_dataSocket)
|
||||
{
|
||||
m_dataSocket = new QUdpSocket(this);
|
||||
}
|
||||
|
||||
if (!m_dataConnected)
|
||||
{
|
||||
connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead())); //, Qt::QueuedConnection);
|
||||
|
||||
if (m_dataSocket->bind(m_dataAddress, m_dataPort))
|
||||
{
|
||||
qDebug("RemoteInputUDPHandler::start: bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort);
|
||||
m_dataConnected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning("RemoteInputUDPHandler::start: cannot bind data port %d", m_dataPort);
|
||||
disconnect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
|
||||
m_dataConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
m_elapsedTimer.start();
|
||||
m_running = true;
|
||||
}
|
||||
|
||||
void RemoteInputUDPHandler::stop()
|
||||
{
|
||||
qDebug("RemoteInputUDPHandler::stop");
|
||||
|
||||
if (!m_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
disconnectTimer();
|
||||
|
||||
if (m_dataConnected)
|
||||
{
|
||||
m_dataConnected = false;
|
||||
disconnect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
|
||||
}
|
||||
|
||||
if (m_dataSocket)
|
||||
{
|
||||
delete m_dataSocket;
|
||||
m_dataSocket = 0;
|
||||
}
|
||||
|
||||
m_centerFrequency = 0;
|
||||
m_samplerate = 0;
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
void RemoteInputUDPHandler::configureUDPLink(const QString& address, quint16 port)
|
||||
{
|
||||
qDebug("RemoteInputUDPHandler::configureUDPLink: %s:%d", address.toStdString().c_str(), port);
|
||||
bool addressOK = m_dataAddress.setAddress(address);
|
||||
|
||||
if (!addressOK)
|
||||
{
|
||||
qWarning("RemoteInputUDPHandler::configureUDPLink: invalid address %s. Set to localhost.", address.toStdString().c_str());
|
||||
m_dataAddress = QHostAddress::LocalHost;
|
||||
}
|
||||
|
||||
m_dataPort = port;
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
|
||||
void RemoteInputUDPHandler::dataReadyRead()
|
||||
{
|
||||
m_udpReadBytes = 0;
|
||||
|
||||
while (m_dataSocket->hasPendingDatagrams() && m_dataConnected)
|
||||
{
|
||||
qint64 pendingDataSize = m_dataSocket->pendingDatagramSize();
|
||||
m_udpReadBytes += m_dataSocket->readDatagram(&m_udpBuf[m_udpReadBytes], pendingDataSize, &m_remoteAddress, 0);
|
||||
|
||||
if (m_udpReadBytes == RemoteUdpSize) {
|
||||
processData();
|
||||
m_udpReadBytes = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputUDPHandler::processData()
|
||||
{
|
||||
m_remoteInputBuffer.writeData(m_udpBuf);
|
||||
const RemoteMetaDataFEC& metaData = m_remoteInputBuffer.getCurrentMeta();
|
||||
bool change = false;
|
||||
|
||||
m_tv_msec = m_remoteInputBuffer.getTVOutMSec();
|
||||
|
||||
if (m_centerFrequency != metaData.m_centerFrequency)
|
||||
{
|
||||
m_centerFrequency = metaData.m_centerFrequency;
|
||||
change = true;
|
||||
}
|
||||
|
||||
if (m_samplerate != metaData.m_sampleRate)
|
||||
{
|
||||
m_samplerate = metaData.m_sampleRate;
|
||||
change = true;
|
||||
}
|
||||
|
||||
if (change && (m_samplerate != 0))
|
||||
{
|
||||
qDebug("RemoteInputUDPHandler::processData: m_samplerate: %u m_centerFrequency: %u kHz", m_samplerate, m_centerFrequency);
|
||||
|
||||
DSPSignalNotification *notif = new DSPSignalNotification(m_samplerate, m_centerFrequency * 1000); // Frequency in Hz for the DSP engine
|
||||
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif);
|
||||
|
||||
if (m_outputMessageQueueToGUI)
|
||||
{
|
||||
RemoteInput::MsgReportRemoteInputStreamData *report = RemoteInput::MsgReportRemoteInputStreamData::create(
|
||||
m_samplerate,
|
||||
m_centerFrequency * 1000, // Frequency in Hz for the GUI
|
||||
m_tv_msec);
|
||||
|
||||
m_outputMessageQueueToGUI->push(report);
|
||||
}
|
||||
|
||||
connectTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputUDPHandler::connectTimer()
|
||||
{
|
||||
if (!m_masterTimerConnected)
|
||||
{
|
||||
qDebug() << "RemoteInputUDPHandler::connectTimer";
|
||||
#ifdef USE_INTERNAL_TIMER
|
||||
#warning "Uses internal timer"
|
||||
connect(m_timer, SIGNAL(timeout()), this, SLOT(tick()));
|
||||
#else
|
||||
connect(&m_masterTimer, SIGNAL(timeout()), this, SLOT(tick()));
|
||||
#endif
|
||||
m_masterTimerConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputUDPHandler::disconnectTimer()
|
||||
{
|
||||
if (m_masterTimerConnected)
|
||||
{
|
||||
qDebug() << "RemoteInputUDPHandler::disconnectTimer";
|
||||
#ifdef USE_INTERNAL_TIMER
|
||||
#warning "Uses internal timer"
|
||||
disconnect(m_timer, SIGNAL(timeout()), this, SLOT(tick()));
|
||||
#else
|
||||
disconnect(&m_masterTimer, SIGNAL(timeout()), this, SLOT(tick()));
|
||||
#endif
|
||||
m_masterTimerConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteInputUDPHandler::tick()
|
||||
{
|
||||
// auto throttling
|
||||
int throttlems = m_elapsedTimer.restart();
|
||||
|
||||
if (throttlems != m_throttlems)
|
||||
{
|
||||
m_throttlems = throttlems;
|
||||
m_readLengthSamples = (m_remoteInputBuffer.getCurrentMeta().m_sampleRate * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000;
|
||||
m_throttleToggle = !m_throttleToggle;
|
||||
}
|
||||
|
||||
if (m_autoCorrBuffer) {
|
||||
m_readLengthSamples += m_remoteInputBuffer.getRWBalanceCorrection();
|
||||
}
|
||||
|
||||
const RemoteMetaDataFEC& metaData = m_remoteInputBuffer.getCurrentMeta();
|
||||
m_readLength = m_readLengthSamples * (metaData.m_sampleBytes & 0xF) * 2;
|
||||
|
||||
if ((metaData.m_sampleBits == 16) && (SDR_RX_SAMP_SZ == 24)) // 16 -> 24 bits
|
||||
{
|
||||
if (m_readLengthSamples > m_converterBufferNbSamples)
|
||||
{
|
||||
if (m_converterBuffer) { delete[] m_converterBuffer; }
|
||||
m_converterBuffer = new int32_t[m_readLengthSamples*2];
|
||||
}
|
||||
|
||||
uint8_t *buf = m_remoteInputBuffer.readData(m_readLength);
|
||||
|
||||
for (unsigned int is = 0; is < m_readLengthSamples; is++)
|
||||
{
|
||||
m_converterBuffer[2*is] = ((int16_t*)buf)[2*is]; // I
|
||||
m_converterBuffer[2*is]<<=8;
|
||||
m_converterBuffer[2*is+1] = ((int16_t*)buf)[2*is+1]; // Q
|
||||
m_converterBuffer[2*is+1]<<=8;
|
||||
}
|
||||
|
||||
m_sampleFifo->write(reinterpret_cast<quint8*>(m_converterBuffer), m_readLengthSamples*sizeof(Sample));
|
||||
}
|
||||
else if ((metaData.m_sampleBits == 24) && (SDR_RX_SAMP_SZ == 16)) // 24 -> 16 bits
|
||||
{
|
||||
if (m_readLengthSamples > m_converterBufferNbSamples)
|
||||
{
|
||||
if (m_converterBuffer) { delete[] m_converterBuffer; }
|
||||
m_converterBuffer = new int32_t[m_readLengthSamples];
|
||||
}
|
||||
|
||||
uint8_t *buf = m_remoteInputBuffer.readData(m_readLength);
|
||||
|
||||
for (unsigned int is = 0; is < m_readLengthSamples; is++)
|
||||
{
|
||||
m_converterBuffer[is] = ((int32_t *)buf)[2*is+1]>>8; // Q -> MSB
|
||||
m_converterBuffer[is] <<=16;
|
||||
m_converterBuffer[is] += ((int32_t *)buf)[2*is]>>8; // I -> LSB
|
||||
}
|
||||
|
||||
m_sampleFifo->write(reinterpret_cast<quint8*>(m_converterBuffer), m_readLengthSamples*sizeof(Sample));
|
||||
}
|
||||
else if ((metaData.m_sampleBits == 16) || (metaData.m_sampleBits == 24)) // same sample size and valid size
|
||||
{
|
||||
// read samples directly feeding the SampleFifo (no callback)
|
||||
m_sampleFifo->write(reinterpret_cast<quint8*>(m_remoteInputBuffer.readData(m_readLength)), m_readLength);
|
||||
m_samplesCount += m_readLengthSamples;
|
||||
}
|
||||
else // invalid size
|
||||
{
|
||||
qWarning("RemoteInputUDPHandler::tick: unexpected sample size in stream: %d bits", (int) metaData.m_sampleBits);
|
||||
}
|
||||
|
||||
if (m_tickCount < m_rateDivider)
|
||||
{
|
||||
m_tickCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tickCount = 0;
|
||||
|
||||
if (m_outputMessageQueueToGUI)
|
||||
{
|
||||
int framesDecodingStatus;
|
||||
int minNbBlocks = m_remoteInputBuffer.getMinNbBlocks();
|
||||
int minNbOriginalBlocks = m_remoteInputBuffer.getMinOriginalBlocks();
|
||||
int nbOriginalBlocks = m_remoteInputBuffer.getCurrentMeta().m_nbOriginalBlocks;
|
||||
int nbFECblocks = m_remoteInputBuffer.getCurrentMeta().m_nbFECBlocks;
|
||||
int sampleBits = m_remoteInputBuffer.getCurrentMeta().m_sampleBits;
|
||||
int sampleBytes = m_remoteInputBuffer.getCurrentMeta().m_sampleBytes;
|
||||
|
||||
//framesDecodingStatus = (minNbOriginalBlocks == nbOriginalBlocks ? 2 : (minNbOriginalBlocks < nbOriginalBlocks - nbFECblocks ? 0 : 1));
|
||||
if (minNbBlocks < nbOriginalBlocks) {
|
||||
framesDecodingStatus = 0;
|
||||
} else if (minNbBlocks < nbOriginalBlocks + nbFECblocks) {
|
||||
framesDecodingStatus = 1;
|
||||
} else {
|
||||
framesDecodingStatus = 2;
|
||||
}
|
||||
|
||||
RemoteInput::MsgReportRemoteInputStreamTiming *report = RemoteInput::MsgReportRemoteInputStreamTiming::create(
|
||||
m_tv_msec,
|
||||
m_remoteInputBuffer.getBufferLengthInSecs(),
|
||||
m_remoteInputBuffer.getBufferGauge(),
|
||||
framesDecodingStatus,
|
||||
minNbBlocks == nbOriginalBlocks + nbFECblocks,
|
||||
minNbBlocks,
|
||||
minNbOriginalBlocks,
|
||||
m_remoteInputBuffer.getMaxNbRecovery(),
|
||||
m_remoteInputBuffer.getAvgNbBlocks(),
|
||||
m_remoteInputBuffer.getAvgOriginalBlocks(),
|
||||
m_remoteInputBuffer.getAvgNbRecovery(),
|
||||
nbOriginalBlocks,
|
||||
nbFECblocks,
|
||||
sampleBits,
|
||||
sampleBytes);
|
||||
|
||||
m_outputMessageQueueToGUI->push(report);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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 //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTUDPHANDLER_H_
|
||||
#define PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTUDPHANDLER_H_
|
||||
|
||||
#include <QObject>
|
||||
#include <QUdpSocket>
|
||||
#include <QHostAddress>
|
||||
#include <QMutex>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include "remoteinputbuffer.h"
|
||||
|
||||
#define REMOTEINPUT_THROTTLE_MS 50
|
||||
|
||||
class SampleSinkFifo;
|
||||
class MessageQueue;
|
||||
class QTimer;
|
||||
class DeviceSourceAPI;
|
||||
|
||||
class RemoteInputUDPHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RemoteInputUDPHandler(SampleSinkFifo* sampleFifo, DeviceSourceAPI *deviceAPI);
|
||||
~RemoteInputUDPHandler();
|
||||
void setMessageQueueToGUI(MessageQueue *queue) { m_outputMessageQueueToGUI = queue; }
|
||||
void start();
|
||||
void stop();
|
||||
void configureUDPLink(const QString& address, quint16 port);
|
||||
void getRemoteAddress(QString& s) const { s = m_remoteAddress.toString(); }
|
||||
int getNbOriginalBlocks() const { return RemoteNbOrginalBlocks; }
|
||||
bool isStreaming() const { return m_masterTimerConnected; }
|
||||
int getSampleRate() const { return m_samplerate; }
|
||||
int getCenterFrequency() const { return m_centerFrequency * 1000; }
|
||||
int getBufferGauge() const { return m_remoteInputBuffer.getBufferGauge(); }
|
||||
uint64_t getTVmSec() const { return m_tv_msec; }
|
||||
int getMinNbBlocks() { return m_remoteInputBuffer.getMinNbBlocks(); }
|
||||
int getMaxNbRecovery() { return m_remoteInputBuffer.getMaxNbRecovery(); }
|
||||
public slots:
|
||||
void dataReadyRead();
|
||||
|
||||
private:
|
||||
DeviceSourceAPI *m_deviceAPI;
|
||||
const QTimer& m_masterTimer;
|
||||
bool m_masterTimerConnected;
|
||||
bool m_running;
|
||||
uint32_t m_rateDivider;
|
||||
RemoteInputBuffer m_remoteInputBuffer;
|
||||
QUdpSocket *m_dataSocket;
|
||||
QHostAddress m_dataAddress;
|
||||
QHostAddress m_remoteAddress;
|
||||
quint16 m_dataPort;
|
||||
bool m_dataConnected;
|
||||
char *m_udpBuf;
|
||||
qint64 m_udpReadBytes;
|
||||
SampleSinkFifo *m_sampleFifo;
|
||||
uint32_t m_samplerate;
|
||||
uint32_t m_centerFrequency;
|
||||
uint64_t m_tv_msec;
|
||||
MessageQueue *m_outputMessageQueueToGUI;
|
||||
uint32_t m_tickCount;
|
||||
std::size_t m_samplesCount;
|
||||
QTimer *m_timer;
|
||||
|
||||
QElapsedTimer m_elapsedTimer;
|
||||
int m_throttlems;
|
||||
uint32_t m_readLengthSamples;
|
||||
uint32_t m_readLength;
|
||||
int32_t *m_converterBuffer;
|
||||
uint32_t m_converterBufferNbSamples;
|
||||
bool m_throttleToggle;
|
||||
bool m_autoCorrBuffer;
|
||||
|
||||
void connectTimer();
|
||||
void disconnectTimer();
|
||||
void processData();
|
||||
|
||||
private slots:
|
||||
void tick();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* PLUGINS_SAMPLESOURCE_REMOTEINPUT_REMOTEINPUTUDPHANDLER_H_ */
|
||||
Reference in New Issue
Block a user